summer-engine 4.0.0 → 4.3.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +15 -11
- package/.cursor-plugin/plugin.json +5 -3
- package/.factory-plugin/plugin.json +3 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +48 -0
- package/README.md +21 -10
- package/dist/cli/command-usage.d.ts +8 -0
- package/dist/cli/command-usage.js +22 -0
- package/dist/cli/commands/config.js +3 -2
- package/dist/cli/commands/create.js +12 -3
- package/dist/cli/commands/list.d.ts +5 -0
- package/dist/cli/commands/list.js +30 -13
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/memory.js +6 -2
- package/dist/cli/commands/ops.d.ts +20 -3
- package/dist/cli/commands/ops.js +60 -27
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +219 -16
- package/dist/cli/commands/skills.js +20 -7
- package/dist/cli/commands/status.d.ts +26 -0
- package/dist/cli/commands/status.js +36 -21
- package/dist/cli/commands/tool.js +4 -0
- package/dist/cli/index.js +2 -0
- package/dist/core/agent-host.d.ts +3 -0
- package/dist/core/agent-host.js +8 -2
- package/dist/core/capabilities/apps.d.ts +10 -10
- package/dist/core/capabilities/audit/args.d.ts +6 -6
- package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
- package/dist/core/capabilities/game-task-plan.d.ts +2 -2
- package/dist/core/capabilities/hdri-import.d.ts +2 -2
- package/dist/core/capabilities/motion-generation.d.ts +2 -2
- package/dist/core/capabilities/placement.d.ts +23 -23
- package/dist/core/capabilities/runtime-control.d.ts +38 -38
- package/dist/core/capabilities/seeing/args.d.ts +16 -16
- package/dist/core/capabilities/ui-control.d.ts +2 -2
- package/dist/core/capabilities/world-snapshot.d.ts +4 -4
- package/dist/core/cli-usage-beacon.d.ts +1 -0
- package/dist/core/cli-usage-beacon.js +18 -0
- package/dist/core/cli-usage.d.ts +49 -0
- package/dist/core/cli-usage.js +108 -0
- package/dist/core/feedback/client.d.ts +1 -1
- package/dist/core/feedback/client.js +3 -2
- package/dist/core/hosted-mcp.d.ts +7 -1
- package/dist/core/hosted-mcp.js +6 -4
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/ops/areas.d.ts +11 -0
- package/dist/core/ops/areas.js +24 -0
- package/dist/core/ops/catalog.d.ts +11 -0
- package/dist/core/ops/catalog.js +24 -1
- package/dist/core/ops/cloud.d.ts +10 -2
- package/dist/core/ops/cloud.js +51 -28
- package/dist/core/ops/contract.d.ts +2 -1
- package/dist/core/ops/contract.js +2 -10
- package/dist/core/ops/public-catalog.d.ts +48 -0
- package/dist/core/ops/public-catalog.js +56 -0
- package/dist/core/ops/search.d.ts +0 -1
- package/dist/core/ops/search.js +169 -37
- package/dist/core/telemetry.d.ts +10 -0
- package/dist/core/telemetry.js +58 -29
- package/dist/installer/agent-config.js +5 -0
- package/dist/installer/agent-table.d.ts +9 -0
- package/dist/installer/agent-table.js +23 -2
- package/dist/installer/cli-permissions.d.ts +65 -0
- package/dist/installer/cli-permissions.js +182 -0
- package/dist/installer/setup.d.ts +2 -0
- package/dist/installer/setup.js +3 -0
- package/dist/installer/skills-refresh.d.ts +29 -0
- package/dist/installer/skills-refresh.js +261 -0
- package/dist/installer/version-check.d.ts +22 -2
- package/dist/installer/version-check.js +66 -9
- package/dist/mcp/ops-surface.js +16 -6
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +38 -8
- package/dist/project-memory/agent-instructions.d.ts +47 -0
- package/dist/project-memory/agent-instructions.js +226 -0
- package/gemini-extension.json +3 -2
- package/integrations/README.md +1 -1
- package/integrations/claude/README.md +6 -0
- package/integrations/claude/manifest-target.json +4 -0
- package/integrations/gemini/README.md +11 -0
- package/integrations/windsurf/README.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +16 -0
- package/library/skills/README.md +5 -4
- package/library/skills/adaptive-music/SKILL.md +21 -0
- package/library/skills/agent-playtesting/SKILL.md +22 -0
- package/library/skills/ai-characters-in-games/SKILL.md +15 -0
- package/library/skills/ambient-bed/SKILL.md +16 -0
- package/library/skills/animated-loop/SKILL.md +19 -0
- package/library/skills/animation-tree/SKILL.md +26 -0
- package/library/skills/art-direction/SKILL.md +14 -0
- package/library/skills/asset-strategy/SKILL.md +30 -0
- package/library/skills/audio-direction/SKILL.md +16 -0
- package/library/skills/auto-fire-targeting/SKILL.md +14 -0
- package/library/skills/brainstorm-game/SKILL.md +13 -0
- package/library/skills/browse-templates/SKILL.md +10 -0
- package/library/skills/camera-rigs/SKILL.md +20 -0
- package/library/skills/character-animation-wiring/SKILL.md +24 -0
- package/library/skills/character-model/SKILL.md +28 -0
- package/library/skills/character-movement/SKILL.md +24 -0
- package/library/skills/character-portrait/SKILL.md +15 -0
- package/library/skills/cinematic-cutscene/SKILL.md +21 -0
- package/library/skills/combat-basics/SKILL.md +19 -0
- package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
- package/library/skills/concept-art/SKILL.md +13 -0
- package/library/skills/concept-to-level/SKILL.md +20 -1
- package/library/skills/concept-to-level/resource.yaml +2 -2
- package/library/skills/create-asset-sheet/SKILL.md +18 -0
- package/library/skills/debug/SKILL.md +32 -0
- package/library/skills/debugging-game-feel/SKILL.md +12 -0
- package/library/skills/design-level/SKILL.md +17 -0
- package/library/skills/design-mechanic/SKILL.md +17 -0
- package/library/skills/design-npc/SKILL.md +21 -0
- package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
- package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
- package/library/skills/environment-kit/SKILL.md +23 -0
- package/library/skills/export-and-ship/SKILL.md +23 -0
- package/library/skills/fabricating-assets/SKILL.md +23 -0
- package/library/skills/facial-and-lipsync/SKILL.md +22 -0
- package/library/skills/fps-controller/SKILL.md +17 -0
- package/library/skills/game-feel/SKILL.md +26 -0
- package/library/skills/gameskill/SKILL.md +11 -0
- package/library/skills/gdscript-patterns/SKILL.md +15 -0
- package/library/skills/generate-motion/SKILL.md +21 -0
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
- package/library/skills/grow-analytics/SKILL.md +10 -0
- package/library/skills/headless-scripting/SKILL.md +13 -0
- package/library/skills/in-game-purchases/SKILL.md +11 -0
- package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
- package/library/skills/make-game/SKILL.md +29 -0
- package/library/skills/multiplayer/SKILL.md +12 -0
- package/library/skills/multiplayer-movement/SKILL.md +18 -0
- package/library/skills/multiplayer-project/SKILL.md +17 -0
- package/library/skills/multiplayer-publish/SKILL.md +17 -5
- package/library/skills/multiplayer-state/SKILL.md +16 -0
- package/library/skills/multiplayer-testing/SKILL.md +15 -0
- package/library/skills/music-track/SKILL.md +16 -0
- package/library/skills/navigate-summer/SKILL.md +15 -0
- package/library/skills/new-project/SKILL.md +11 -0
- package/library/skills/organic-model/SKILL.md +24 -0
- package/library/skills/pixel-art/SKILL.md +15 -0
- package/library/skills/play/SKILL.md +18 -0
- package/library/skills/playtesting-a-feature/SKILL.md +23 -0
- package/library/skills/procedural-animation/SKILL.md +18 -0
- package/library/skills/prop-model/SKILL.md +23 -0
- package/library/skills/publish-your-game/SKILL.md +27 -0
- package/library/skills/release-readiness/SKILL.md +12 -0
- package/library/skills/remote-deploy/SKILL.md +14 -0
- package/library/skills/retarget/SKILL.md +20 -0
- package/library/skills/running-in-the-cloud/SKILL.md +10 -0
- package/library/skills/save-load/SKILL.md +15 -0
- package/library/skills/scene-composition/SKILL.md +17 -0
- package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
- package/library/skills/scene-scripting/SKILL.md +30 -0
- package/library/skills/scene-to-level/SKILL.md +22 -0
- package/library/skills/skill-test/SKILL.md +10 -0
- package/library/skills/skybox-panorama/SKILL.md +20 -0
- package/library/skills/sound-effect/SKILL.md +23 -0
- package/library/skills/spatial-placement/SKILL.md +30 -0
- package/library/skills/sprite-sheet/SKILL.md +23 -0
- package/library/skills/store-art/SKILL.md +11 -0
- package/library/skills/store-listing/SKILL.md +10 -0
- package/library/skills/summer-analytics/SKILL.md +15 -0
- package/library/skills/summer-apps/SKILL.md +29 -0
- package/library/skills/summer-assets/SKILL.md +31 -1
- package/library/skills/summer-assets/resource.yaml +2 -2
- package/library/skills/summer-cli/SKILL.md +133 -0
- package/library/skills/summer-cli/resource.yaml +31 -0
- package/library/skills/summer-friends/SKILL.md +14 -0
- package/library/skills/summer-leaderboards/SKILL.md +16 -0
- package/library/skills/summer-match-results/SKILL.md +17 -0
- package/library/skills/summer-matchmaking/SKILL.md +17 -0
- package/library/skills/summer-parties/SKILL.md +14 -0
- package/library/skills/summer-player-data/SKILL.md +14 -0
- package/library/skills/summer-store/SKILL.md +15 -0
- package/library/skills/summer-world-chat/SKILL.md +14 -0
- package/library/skills/summer-world-saves/SKILL.md +14 -0
- package/library/skills/text-to-motion/SKILL.md +15 -0
- package/library/skills/tileable-texture/SKILL.md +18 -0
- package/library/skills/trailer-shot/SKILL.md +18 -0
- package/library/skills/tune-performance/SKILL.md +26 -0
- package/library/skills/ui-basics/SKILL.md +14 -0
- package/library/skills/ui-graphics/SKILL.md +17 -0
- package/library/skills/use-widget-asset/SKILL.md +14 -0
- package/library/skills/using-summer/SKILL.md +26 -0
- package/library/skills/using-summer/resource.yaml +2 -1
- package/library/skills/vehicle-model/SKILL.md +27 -0
- package/library/skills/verifying-scenes/SKILL.md +29 -0
- package/library/skills/vfx-dissolve/SKILL.md +19 -0
- package/library/skills/vfx-fire/SKILL.md +26 -0
- package/library/skills/vfx-hit-spark/SKILL.md +23 -0
- package/library/skills/vfx-lightning/SKILL.md +23 -0
- package/library/skills/vfx-magic-glow/SKILL.md +23 -0
- package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
- package/library/skills/vfx-smoke/SKILL.md +26 -0
- package/library/skills/vfx-water-ripple/SKILL.md +23 -0
- package/library/skills/voice-line/SKILL.md +16 -0
- package/library/skills/web-and-touch-input/SKILL.md +19 -0
- package/library/skills/web-compatibility-look/SKILL.md +20 -0
- package/library/skills/world-building-3d/SKILL.md +28 -0
- package/package.json +3 -2
- package/registry/generated/counts.json +2 -2
- package/registry/generated/gemini-extension.json +3 -2
- package/registry/generated/index.json +157 -118
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude-directory.json +168 -0
- package/registry/generated/plugin.claude.json +3 -2
- package/registry/generated/plugin.codex.json +15 -11
- package/registry/generated/plugin.cursor.json +5 -3
- package/registry/generated/plugin.factory.json +3 -2
- package/registry/generated/server.json +11 -2
- package/registry/generated/skills-index.md +5 -4
- package/registry/generated/skills-registry.json +16 -2
- package/registry/operations/legacy-names.json +35 -0
- package/registry/operations/public-catalog.json +33160 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { chmod, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { CLI_AREAS } from "../core/ops/areas.js";
|
|
5
|
+
import { localEntries } from "../core/ops/catalog.js";
|
|
6
|
+
import { shippedCloudEntries } from "../core/ops/public-catalog.js";
|
|
7
|
+
/**
|
|
8
|
+
* Allow rules for the CLI path, added only with `summer setup <agent> --cli
|
|
9
|
+
* --allow-rules --yes` (the flag is in the command the person approves), so
|
|
10
|
+
* the agent's read-only and dry-run summer commands do not each ask:
|
|
11
|
+
*
|
|
12
|
+
* - the reads in AUTO_ALLOWED_READS, as they are. The list is reviewed by
|
|
13
|
+
* hand: a new read operation in the catalog does not join it, and
|
|
14
|
+
* cli-permissions.test.ts fails until someone adds it on purpose;
|
|
15
|
+
* - every spend (generation and the rest) only as a dry run, `--dry-run`
|
|
16
|
+
* right after the action. A call with that flag is a dry run whatever else
|
|
17
|
+
* it says (parseCallFlags), so the rule cannot let a real spend through.
|
|
18
|
+
*
|
|
19
|
+
* Changes, publishing, prices, deletes and messages keep asking. Claude Code
|
|
20
|
+
* matches the command text (`Bash(summer jobs wait *)` matches the bare
|
|
21
|
+
* command and `summer jobs wait --job-id x`, not `summer jobs waitx`); Codex
|
|
22
|
+
* matches argument lists, in a rules file of Summer's own. Both get the
|
|
23
|
+
* `summer` and the `npx -y summer-engine@latest` forms.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Reads that run without asking. Each one reads Summer data or the open
|
|
27
|
+
* editor's state; `jobs wait` and `assets get` also save the files a finished
|
|
28
|
+
* job names (media files only, never over an existing file). Left out on
|
|
29
|
+
* purpose: `engine capture-gameplay` (it starts Summer Engine on a project, so
|
|
30
|
+
* the project's own code runs, and writes to any --out folder) and
|
|
31
|
+
* `studio open` (it opens a page in the browser).
|
|
32
|
+
*/
|
|
33
|
+
export const AUTO_ALLOWED_READS = [
|
|
34
|
+
"whoami", "ops.search", "ops.describe",
|
|
35
|
+
"docs.search", "docs.read", "docs.engine_api", "docs.plan",
|
|
36
|
+
"jobs.get", "jobs.list", "jobs.wait",
|
|
37
|
+
"account.permissions", "profile.get", "team.list", "chats.list", "plugins.search",
|
|
38
|
+
"games.get", "games.list", "games.members", "jams.list", "pricing.get",
|
|
39
|
+
"store.get_listing", "store.status", "experiments.list", "experiments.results",
|
|
40
|
+
"analytics.event_limit", "analytics.events", "analytics.funnel", "analytics.overview", "analytics.summary", "analytics.trend",
|
|
41
|
+
"assets.choices", "assets.get", "assets.get_icon_collection", "assets.get_pack", "assets.list_comments", "assets.list_folders", "assets.list_icon_collections", "assets.search",
|
|
42
|
+
"apps.events", "apps.get", "apps.guide", "apps.list", "apps.page", "apps.read_data", "apps.screenshot", "apps.tools",
|
|
43
|
+
"plans.changes", "plans.chat", "plans.directions", "plans.get", "plans.links", "plans.list", "plans.presence", "plans.show",
|
|
44
|
+
"projects.builds", "projects.get", "projects.list", "projects.open_in_engine", "projects.preview_build",
|
|
45
|
+
"stories.get", "stories.list", "studio.map", "studio.page", "studio.read_form",
|
|
46
|
+
"marketing.applications", "marketing.content_feed", "marketing.content_videos", "marketing.get_brand_pack", "marketing.list_content_games",
|
|
47
|
+
"engine.debug_views", "engine.game_probe", "engine.get_console", "engine.get_debugger_errors", "engine.get_debugger_warnings", "engine.get_diagnostics",
|
|
48
|
+
"engine.get_project_context", "engine.get_runtime_tree", "engine.get_scene_tree", "engine.get_script_errors", "engine.grep", "engine.inspect_asset",
|
|
49
|
+
"engine.inspect_node", "engine.inspect_resource", "engine.inspect_runtime_node", "engine.is_running", "engine.list_camera_bookmarks", "engine.list_ui_actions",
|
|
50
|
+
"engine.measure", "engine.navigation_probe", "engine.raycast", "engine.read_file", "engine.recent_events", "engine.scene_audit", "engine.screenshot",
|
|
51
|
+
"engine.shot_sheet", "engine.snapshot_diff", "engine.starcast", "engine.test_placement", "engine.ui_screenshot", "engine.ui_tree", "engine.wait_for_event",
|
|
52
|
+
"engine.world_snapshot", "engine.zoom",
|
|
53
|
+
];
|
|
54
|
+
/** The shell words of a command, after the launcher: `assets.generate_image` -> `assets generate-image`. */
|
|
55
|
+
const wordsOf = (op) => (op.includes(".") ? [op.split(".")[0], op.split(".")[1].replace(/_/g, "-")] : [op]);
|
|
56
|
+
/** The commands allowed without asking: `summer status`, the reviewed reads, and a dry run of every spend in the catalog. */
|
|
57
|
+
export function allowedCliCommands() {
|
|
58
|
+
const known = new Map([...localEntries(), ...shippedCloudEntries()].map((entry) => [entry.name, entry]));
|
|
59
|
+
const reads = AUTO_ALLOWED_READS.filter((op) => known.get(op)?.effect === "read").map((op) => ({ words: wordsOf(op), dryRun: false }));
|
|
60
|
+
const areas = new Set(CLI_AREAS);
|
|
61
|
+
const spends = [...known.values()]
|
|
62
|
+
.filter((entry) => entry.effect === "spend" && entry.status !== "deprecated" && areas.has(entry.name.split(".")[0]))
|
|
63
|
+
.map((entry) => ({ words: wordsOf(entry.name), dryRun: true }));
|
|
64
|
+
return [{ words: ["status"], dryRun: false }, ...reads, ...spends].sort((a, b) => a.words.join(" ").localeCompare(b.words.join(" ")) || Number(a.dryRun) - Number(b.dryRun));
|
|
65
|
+
}
|
|
66
|
+
export const LAUNCHERS = [["summer"], ["npx", "-y", "summer-engine@latest"]];
|
|
67
|
+
/** Claude Code `permissions.allow` rules, for both launchers. */
|
|
68
|
+
export function claudeAllowRules() {
|
|
69
|
+
return LAUNCHERS.flatMap((launcher) => allowedCliCommands().map(({ words, dryRun }) => `Bash(${[...launcher, ...words, ...(dryRun ? ["--dry-run"] : [])].join(" ")} *)`));
|
|
70
|
+
}
|
|
71
|
+
const starlark = (value) => (typeof value === "string" ? JSON.stringify(value) : `[${value.map((item) => JSON.stringify(item)).join(", ")}]`);
|
|
72
|
+
const CODEX_HEADER = "# Written by summer setup codex --cli --allow-rules; setup rewrites this file and --remove-allow-rules deletes it.";
|
|
73
|
+
/** Codex rules: one prefix_rule per launcher, area and kind, the actions as a union. */
|
|
74
|
+
export function codexRules() {
|
|
75
|
+
const groups = new Map();
|
|
76
|
+
for (const launcher of LAUNCHERS) {
|
|
77
|
+
for (const { words, dryRun } of allowedCliCommands()) {
|
|
78
|
+
const head = words.length > 1 ? words.slice(0, 1) : [];
|
|
79
|
+
const key = `${launcher.join(" ")}|${head.join(" ")}|${dryRun}`;
|
|
80
|
+
const group = groups.get(key) ?? { launcher, head, actions: [], dryRun };
|
|
81
|
+
group.actions.push(words[words.length - 1]);
|
|
82
|
+
groups.set(key, group);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const rules = [...groups.values()].map(({ launcher, head, actions, dryRun }) => {
|
|
86
|
+
const pattern = [...launcher.map(starlark), ...head.map(starlark), starlark(actions.length === 1 ? actions[0] : actions), ...(dryRun ? [starlark("--dry-run")] : [])];
|
|
87
|
+
const example = [...launcher, ...head, actions[0], ...(dryRun ? ["--dry-run"] : [])].join(" ");
|
|
88
|
+
const why = dryRun ? "Summer dry run: checks the input, spends nothing" : "Summer read: changes no Summer data; jobs wait and assets get save the files they name";
|
|
89
|
+
return `prefix_rule(\n pattern = [${pattern.join(", ")}],\n decision = "allow",\n justification = ${starlark(why)},\n match = [${starlark(example)}],\n)`;
|
|
90
|
+
});
|
|
91
|
+
return `${CODEX_HEADER}\n# Reads, and spends only as dry runs. Changes, publishing, deletes and messages still ask.\n\n${rules.join("\n\n")}\n`;
|
|
92
|
+
}
|
|
93
|
+
/** The agents with clean per-command allow rules: Claude Code (settings.json) and Codex (rules files). */
|
|
94
|
+
export function supportsAllowRules(agent) {
|
|
95
|
+
return agent === "claude-code" || agent === "codex";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Where the rules go. Claude Code: the user's settings, or with --scope
|
|
99
|
+
* project the project's personal settings.local.json (never the shared file).
|
|
100
|
+
* Codex: always the user's rules folder; a project's .codex/rules is shared
|
|
101
|
+
* through git, so Summer does not write there.
|
|
102
|
+
*/
|
|
103
|
+
export function allowRulesPath(agent, scope, ctx) {
|
|
104
|
+
if (agent === "codex")
|
|
105
|
+
return ctx.env.SUMMER_CODEX_RULES_FILE || join(ctx.env.CODEX_HOME || join(ctx.home, ".codex"), "rules", "summer.rules");
|
|
106
|
+
return ctx.env.SUMMER_CLAUDE_SETTINGS_FILE || (scope === "project" ? join(ctx.cwd, ".claude", "settings.local.json") : join(ctx.home, ".claude", "settings.json"));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Write next to the file, then rename over it, so Claude Code never reads
|
|
110
|
+
* half a file. A symlinked settings file (a dotfiles repo) is written through
|
|
111
|
+
* the link, in its real folder, and keeps its mode.
|
|
112
|
+
*/
|
|
113
|
+
async function writeAtomically(path, text) {
|
|
114
|
+
const target = existsSync(path) ? await realpath(path) : path;
|
|
115
|
+
await mkdir(dirname(target), { recursive: true });
|
|
116
|
+
const mode = existsSync(target) ? (await stat(target)).mode & 0o7777 : undefined;
|
|
117
|
+
const temp = `${target}.summer-${process.pid}.tmp`;
|
|
118
|
+
await writeFile(temp, text, mode === undefined ? {} : { mode });
|
|
119
|
+
if (mode !== undefined)
|
|
120
|
+
await chmod(temp, mode);
|
|
121
|
+
await rename(temp, target);
|
|
122
|
+
}
|
|
123
|
+
async function readClaudeSettings(path) {
|
|
124
|
+
let settings = {};
|
|
125
|
+
if (existsSync(path)) {
|
|
126
|
+
try {
|
|
127
|
+
settings = JSON.parse(await readFile(path, "utf8"));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error(`${path} is not valid JSON, so Summer left its allow rules alone. Fix the file, then run setup again.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
134
|
+
throw new Error(`${path} is not a JSON object, so Summer left its allow rules alone.`);
|
|
135
|
+
const record = settings;
|
|
136
|
+
const permissions = record.permissions ?? {};
|
|
137
|
+
if (!permissions || typeof permissions !== "object" || Array.isArray(permissions))
|
|
138
|
+
throw new Error(`${path}: permissions is not an object, so Summer left its allow rules alone.`);
|
|
139
|
+
const allow = permissions.allow ?? [];
|
|
140
|
+
if (!Array.isArray(allow))
|
|
141
|
+
throw new Error(`${path}: permissions.allow is not a list, so Summer left its allow rules alone.`);
|
|
142
|
+
return { settings: record, permissions: permissions, allow };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Add the allow rules. Claude Code: merged into `permissions.allow`, nothing
|
|
146
|
+
* removed, every other setting kept. Codex: Summer's own rules file,
|
|
147
|
+
* rewritten. Writes nothing unless `write`.
|
|
148
|
+
*/
|
|
149
|
+
export async function addAllowRules(agent, path, write) {
|
|
150
|
+
if (agent === "codex") {
|
|
151
|
+
const text = codexRules();
|
|
152
|
+
const rules = text.split("\n").filter((line) => line.startsWith(" pattern = ")).map((line) => line.trim());
|
|
153
|
+
const changed = (existsSync(path) ? await readFile(path, "utf8") : "") !== text;
|
|
154
|
+
if (write && changed)
|
|
155
|
+
await writeAtomically(path, text);
|
|
156
|
+
return { agent, path, rules, changed: changed ? rules : [], wrote: write && changed };
|
|
157
|
+
}
|
|
158
|
+
const rules = claudeAllowRules();
|
|
159
|
+
const { settings, permissions, allow } = await readClaudeSettings(path);
|
|
160
|
+
const added = rules.filter((rule) => !allow.includes(rule));
|
|
161
|
+
if (write && added.length > 0)
|
|
162
|
+
await writeAtomically(path, `${JSON.stringify({ ...settings, permissions: { ...permissions, allow: [...allow, ...added] } }, null, 2)}\n`);
|
|
163
|
+
return { agent, path, rules, changed: added, wrote: write && added.length > 0 };
|
|
164
|
+
}
|
|
165
|
+
/** Remove exactly the rules Summer adds (Claude Code), or Summer's own rules file (Codex). Writes nothing unless `write`. */
|
|
166
|
+
export async function removeAllowRules(agent, path, write) {
|
|
167
|
+
if (agent === "codex") {
|
|
168
|
+
const ours = existsSync(path) && (await readFile(path, "utf8")).startsWith("# Written by summer setup codex");
|
|
169
|
+
if (write && ours)
|
|
170
|
+
await rm(path);
|
|
171
|
+
return { agent, path, rules: [], changed: ours ? [path] : [], wrote: write && ours };
|
|
172
|
+
}
|
|
173
|
+
if (!existsSync(path))
|
|
174
|
+
return { agent, path, rules: [], changed: [], wrote: false };
|
|
175
|
+
const ours = new Set(claudeAllowRules());
|
|
176
|
+
const { settings, permissions, allow } = await readClaudeSettings(path);
|
|
177
|
+
const removed = allow.filter((rule) => typeof rule === "string" && ours.has(rule));
|
|
178
|
+
if (write && removed.length > 0) {
|
|
179
|
+
await writeAtomically(path, `${JSON.stringify({ ...settings, permissions: { ...permissions, allow: allow.filter((rule) => !removed.includes(rule)) } }, null, 2)}\n`);
|
|
180
|
+
}
|
|
181
|
+
return { agent, path, rules: [], changed: removed, wrote: write && removed.length > 0 };
|
|
182
|
+
}
|
|
@@ -38,6 +38,8 @@ export interface SkillSetupOptions {
|
|
|
38
38
|
stableOnly?: boolean;
|
|
39
39
|
/** Install only the asset skills (`setup --assets-only`). */
|
|
40
40
|
assetsOnly?: boolean;
|
|
41
|
+
/** false: no Claude Code slash commands (`setup --cli`; they start work through the MCP tools). */
|
|
42
|
+
commands?: boolean;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* The preview clause of the setup summary — one wording for the result message
|
package/dist/installer/setup.js
CHANGED
|
@@ -44,6 +44,7 @@ export function setupSkills(agent, options) {
|
|
|
44
44
|
recommended,
|
|
45
45
|
stableOnly,
|
|
46
46
|
assetsOnly,
|
|
47
|
+
commands: options.commands !== false,
|
|
47
48
|
});
|
|
48
49
|
if (!invocation) {
|
|
49
50
|
return {
|
|
@@ -145,6 +146,8 @@ function skillInstallInvocation(agent, opts) {
|
|
|
145
146
|
baseArgs.push("--force");
|
|
146
147
|
if (opts.stableOnly)
|
|
147
148
|
baseArgs.push("--stable-only");
|
|
149
|
+
if (!opts.commands)
|
|
150
|
+
baseArgs.push("--no-commands");
|
|
148
151
|
return {
|
|
149
152
|
command,
|
|
150
153
|
args: [...prefix, ...baseArgs],
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type SkillRegistryEntry } from "../core/skills-registry.js";
|
|
2
|
+
export interface RefreshedFolder {
|
|
3
|
+
dir: string;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
/** Skills replaced with this version's copy. */
|
|
7
|
+
updated: string[];
|
|
8
|
+
/** New library skills added to a full install. */
|
|
9
|
+
added: string[];
|
|
10
|
+
/** Summer's skills someone changed after install: left as they are. */
|
|
11
|
+
kept: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface RefreshFailure {
|
|
14
|
+
dir: string;
|
|
15
|
+
skill?: string;
|
|
16
|
+
code: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RefreshResult {
|
|
19
|
+
refreshed: RefreshedFolder[];
|
|
20
|
+
failures: RefreshFailure[];
|
|
21
|
+
}
|
|
22
|
+
/** User-scope skill folders with one folder per skill, each listed once. */
|
|
23
|
+
export declare function skillFolderLocations(): string[];
|
|
24
|
+
export declare function refreshInstalledSkills(cliVersion: string, options?: {
|
|
25
|
+
dirs?: string[];
|
|
26
|
+
registry?: readonly SkillRegistryEntry[];
|
|
27
|
+
}): RefreshResult;
|
|
28
|
+
/** The one message a person and their agent see after a refresh, or null when nothing changed. */
|
|
29
|
+
export declare function refreshNotice(refreshed: readonly RefreshedFolder[], failures?: readonly RefreshFailure[]): string | null;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { closeSync, cpSync, existsSync, lstatSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getSkillRegistry, resolveSkillDir } from "../core/skills-registry.js";
|
|
4
|
+
import { allAgentSpecs, defaultPathContext, resolveSkillPath } from "./agent-table.js";
|
|
5
|
+
import { compareSemver, parseSemver, readKeptSkills, readSkillMarker, SKILL_VERSION_MARKER_FILENAME } from "./version-check.js";
|
|
6
|
+
/**
|
|
7
|
+
* After an upgrade, bring the Summer skills already installed on this
|
|
8
|
+
* computer up to this CLI's version. Skills written for 3.x name 3.x tools,
|
|
9
|
+
* and while their marker says 3.x the MCP server keeps the legacy toolset on
|
|
10
|
+
* (legacy-toolset.ts), which gives back most of 4.0's smaller tool list.
|
|
11
|
+
*
|
|
12
|
+
* Only what Summer installed and nobody changed is replaced, in a skills
|
|
13
|
+
* folder that carries Summer's `.summer-version` marker older than this CLI:
|
|
14
|
+
* a real folder (not a link) whose `resource.yaml` says `id: skill/<name>`
|
|
15
|
+
* (every Summer release ships it; other packs reuse names such as
|
|
16
|
+
* `brainstorming`), with no file changed after the marker was written, and
|
|
17
|
+
* not one an earlier refresh already left for that reason (the marker lists
|
|
18
|
+
* them, so the protection lasts until `summer setup --force`). A copy that
|
|
19
|
+
* already matches the library counts as current, not as changed. Each
|
|
20
|
+
* replacement is a copy beside it and two renames, so a kill or a copy error
|
|
21
|
+
* leaves the old skill in place. One process per folder (a lock file).
|
|
22
|
+
* Nothing is removed. Missing library skills are added only to a folder that
|
|
23
|
+
* holds nearly the whole library, so an assets-only install stays one.
|
|
24
|
+
* Rule-file installs (Cline, Roo Code, old Cursor rules) keep the refresh
|
|
25
|
+
* command `summer doctor` prints.
|
|
26
|
+
*/
|
|
27
|
+
const LOCK_FILE = ".summer-refresh.lock";
|
|
28
|
+
const STALE_LOCK_MS = 60_000;
|
|
29
|
+
/** Setup writes the marker right after its copy; a file changed later than this was changed by someone. */
|
|
30
|
+
const EDIT_GRACE_MS = 60_000;
|
|
31
|
+
/** A folder holding this share of the bundled library is a full install: new library skills join it. */
|
|
32
|
+
const FULL_INSTALL_SHARE = 0.9;
|
|
33
|
+
const TEMP_PREFIX = ".summer-refresh-";
|
|
34
|
+
const OLD_PREFIX = ".summer-old-";
|
|
35
|
+
/** User-scope skill folders with one folder per skill, each listed once. */
|
|
36
|
+
export function skillFolderLocations() {
|
|
37
|
+
const ctx = defaultPathContext();
|
|
38
|
+
const dirs = new Set([join(ctx.home, ".summer", "skills")]);
|
|
39
|
+
for (const spec of allAgentSpecs()) {
|
|
40
|
+
const home = resolveSkillPath(spec, "user", ctx);
|
|
41
|
+
if (home?.kind === "skill-dir")
|
|
42
|
+
dirs.add(home.path);
|
|
43
|
+
}
|
|
44
|
+
return [...dirs];
|
|
45
|
+
}
|
|
46
|
+
const isPrerelease = (version) => /^\d+\.\d+\.\d+-/.test(version.trim());
|
|
47
|
+
/** Refresh when the marker is older, or a pre-release of this release (4.2.0-next.1 -> 4.2.0, never back). */
|
|
48
|
+
function isBehind(marker, cliVersion) {
|
|
49
|
+
const installed = parseSemver(marker.version);
|
|
50
|
+
const current = parseSemver(cliVersion);
|
|
51
|
+
if (!installed || !current)
|
|
52
|
+
return false;
|
|
53
|
+
const order = compareSemver(installed, current);
|
|
54
|
+
return order < 0 || (order === 0 && isPrerelease(marker.version) && !isPrerelease(cliVersion));
|
|
55
|
+
}
|
|
56
|
+
const errorCode = (error) => error?.code ?? (error instanceof Error ? error.message : String(error));
|
|
57
|
+
/** Summer's own copy: a real folder (not a link) whose resource.yaml names this skill. */
|
|
58
|
+
function isSummersSkill(dest, name) {
|
|
59
|
+
try {
|
|
60
|
+
if (!lstatSync(dest).isDirectory())
|
|
61
|
+
return false;
|
|
62
|
+
const id = readFileSync(join(dest, "resource.yaml"), "utf-8").match(/^id:\s*["']?([^"'\s]+)["']?\s*$/m)?.[1];
|
|
63
|
+
return id === `skill/${name}`;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** The folder, or any file in it, changed after `since` (ms); the folder's own time covers a deleted file. Links are not followed. */
|
|
70
|
+
function changedSince(dir, since) {
|
|
71
|
+
if (lstatSync(dir).mtimeMs > since)
|
|
72
|
+
return true;
|
|
73
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
74
|
+
const path = join(dir, entry.name);
|
|
75
|
+
if (lstatSync(path).mtimeMs > since)
|
|
76
|
+
return true;
|
|
77
|
+
if (entry.isDirectory() && changedSince(path, since))
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
/** Relative paths of every file under `dir`. */
|
|
83
|
+
function filesUnder(dir, prefix = "") {
|
|
84
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? filesUnder(join(dir, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]);
|
|
85
|
+
}
|
|
86
|
+
/** The installed folder holds exactly the library's files with the library's bytes (a refresh a kill interrupted already wrote it). */
|
|
87
|
+
function sameAsLibrary(dest, src) {
|
|
88
|
+
try {
|
|
89
|
+
const installed = filesUnder(dest).sort();
|
|
90
|
+
const library = filesUnder(src).sort();
|
|
91
|
+
return (installed.length === library.length &&
|
|
92
|
+
installed.every((file, index) => file === library[index] && readFileSync(join(dest, file)).equals(readFileSync(join(src, file)))));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Copy beside, rename the old one aside, rename the new one in; the old one comes back on any error. */
|
|
99
|
+
function replaceSkill(dir, name, src) {
|
|
100
|
+
const dest = join(dir, name);
|
|
101
|
+
const temp = join(dir, `${TEMP_PREFIX}${name}-${process.pid}`);
|
|
102
|
+
const old = join(dir, `${OLD_PREFIX}${name}-${process.pid}`);
|
|
103
|
+
rmSync(temp, { recursive: true, force: true });
|
|
104
|
+
try {
|
|
105
|
+
cpSync(src, temp, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
rmSync(temp, { recursive: true, force: true });
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
const hadOld = existsSync(dest);
|
|
112
|
+
if (hadOld)
|
|
113
|
+
renameSync(dest, old);
|
|
114
|
+
try {
|
|
115
|
+
renameSync(temp, dest);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (hadOld)
|
|
119
|
+
renameSync(old, dest);
|
|
120
|
+
rmSync(temp, { recursive: true, force: true });
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
if (hadOld)
|
|
124
|
+
rmSync(old, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
/** What a killed refresh left: half copies go, a skill renamed aside comes back. */
|
|
127
|
+
function recoverInterrupted(dir) {
|
|
128
|
+
for (const entry of readdirSync(dir)) {
|
|
129
|
+
const path = join(dir, entry);
|
|
130
|
+
if (entry.startsWith(TEMP_PREFIX))
|
|
131
|
+
rmSync(path, { recursive: true, force: true });
|
|
132
|
+
if (!entry.startsWith(OLD_PREFIX))
|
|
133
|
+
continue;
|
|
134
|
+
const name = entry.slice(OLD_PREFIX.length).replace(/-\d+$/, "");
|
|
135
|
+
if (existsSync(join(dir, name)))
|
|
136
|
+
rmSync(path, { recursive: true, force: true });
|
|
137
|
+
else
|
|
138
|
+
renameSync(path, join(dir, name));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** The folder's lock, or null while another process holds a fresh one. */
|
|
142
|
+
function takeLock(dir) {
|
|
143
|
+
const path = join(dir, LOCK_FILE);
|
|
144
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
145
|
+
try {
|
|
146
|
+
closeSync(openSync(path, "wx"));
|
|
147
|
+
return () => rmSync(path, { force: true });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (errorCode(error) !== "EEXIST")
|
|
151
|
+
throw error;
|
|
152
|
+
try {
|
|
153
|
+
if (Date.now() - statSync(path).mtimeMs < STALE_LOCK_MS)
|
|
154
|
+
return null;
|
|
155
|
+
unlinkSync(path);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function refreshFolder(dir, marker, cliVersion, registry, failures) {
|
|
165
|
+
const release = takeLock(dir);
|
|
166
|
+
if (!release)
|
|
167
|
+
return null;
|
|
168
|
+
try {
|
|
169
|
+
recoverInterrupted(dir);
|
|
170
|
+
// The marker must be writable before anything is copied, or every start would copy again.
|
|
171
|
+
const markerTemp = join(dir, `${SKILL_VERSION_MARKER_FILENAME}.tmp`);
|
|
172
|
+
writeFileSync(markerTemp, "", "utf-8");
|
|
173
|
+
const installedAt = Date.parse(marker.installedAt);
|
|
174
|
+
const editedAfter = (Number.isFinite(installedAt) ? installedAt : 0) + EDIT_GRACE_MS;
|
|
175
|
+
const folder = { dir, from: marker.version, to: cliVersion, updated: [], added: [], kept: [] };
|
|
176
|
+
const keptBefore = new Set(readKeptSkills(dir));
|
|
177
|
+
const owned = registry.filter((entry) => isSummersSkill(join(dir, entry.name), entry.name));
|
|
178
|
+
for (const entry of owned) {
|
|
179
|
+
const dest = join(dir, entry.name);
|
|
180
|
+
if (sameAsLibrary(dest, resolveSkillDir(entry))) {
|
|
181
|
+
folder.updated.push(entry.name);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (keptBefore.has(entry.name) || changedSince(dest, editedAfter)) {
|
|
185
|
+
folder.kept.push(entry.name);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
replaceSkill(dir, entry.name, resolveSkillDir(entry));
|
|
190
|
+
folder.updated.push(entry.name);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
failures.push({ dir, skill: entry.name, code: errorCode(error) });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const bundled = registry.filter((entry) => entry.bundled !== false && entry.status !== "deprecated");
|
|
197
|
+
if (bundled.length > 0 && owned.length >= FULL_INSTALL_SHARE * bundled.length) {
|
|
198
|
+
for (const entry of bundled) {
|
|
199
|
+
if (existsSync(join(dir, entry.name)))
|
|
200
|
+
continue;
|
|
201
|
+
try {
|
|
202
|
+
replaceSkill(dir, entry.name, resolveSkillDir(entry));
|
|
203
|
+
folder.added.push(entry.name);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
failures.push({ dir, skill: entry.name, code: errorCode(error) });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Written last, through a rename: a kill before this leaves the old marker, and the next start finishes.
|
|
211
|
+
const next = { version: cliVersion, installedAt: new Date().toISOString(), ...(folder.kept.length > 0 ? { kept: folder.kept } : {}) };
|
|
212
|
+
writeFileSync(markerTemp, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
213
|
+
renameSync(markerTemp, join(dir, SKILL_VERSION_MARKER_FILENAME));
|
|
214
|
+
return folder;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
release();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export function refreshInstalledSkills(cliVersion, options = {}) {
|
|
221
|
+
const result = { refreshed: [], failures: [] };
|
|
222
|
+
if (!parseSemver(cliVersion))
|
|
223
|
+
return result;
|
|
224
|
+
for (const dir of options.dirs ?? skillFolderLocations()) {
|
|
225
|
+
const marker = readSkillMarker(dir);
|
|
226
|
+
if (!marker || !isBehind(marker, cliVersion))
|
|
227
|
+
continue;
|
|
228
|
+
try {
|
|
229
|
+
const folder = refreshFolder(dir, marker, cliVersion, options.registry ?? getSkillRegistry(), result.failures);
|
|
230
|
+
if (folder)
|
|
231
|
+
result.refreshed.push(folder);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
// A folder we cannot lock or write keeps its skills and its marker; doctor still names the refresh.
|
|
235
|
+
result.failures.push({ dir, code: errorCode(error) });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
const listed = (names) => names.length > 5 ? `${names.slice(0, 5).join(", ")} and ${names.length - 5} more` : names.join(", ");
|
|
241
|
+
/** The one message a person and their agent see after a refresh, or null when nothing changed. */
|
|
242
|
+
export function refreshNotice(refreshed, failures = []) {
|
|
243
|
+
const skills = new Set(refreshed.flatMap((folder) => [...folder.updated, ...folder.added])).size;
|
|
244
|
+
const kept = [...new Set(refreshed.flatMap((folder) => folder.kept))];
|
|
245
|
+
const failed = [...new Set(failures.flatMap((failure) => (failure.skill ? [failure.skill] : [])))];
|
|
246
|
+
if (skills === 0 && kept.length === 0)
|
|
247
|
+
return null;
|
|
248
|
+
const changedFolders = refreshed.filter((folder) => folder.updated.length + folder.added.length > 0).length;
|
|
249
|
+
const from = [...new Set(refreshed.map((folder) => folder.from))].join(", ");
|
|
250
|
+
const to = refreshed[0].to;
|
|
251
|
+
return [
|
|
252
|
+
skills > 0 ? `Summer updated its ${skills} installed skills (${changedFolders === 1 ? "1 folder" : `${changedFolders} folders`}) from ${from} to ${to}.` : "",
|
|
253
|
+
kept.length > 0
|
|
254
|
+
? `It left ${kept.length} Summer skills you changed as they are (${listed(kept)}); summer setup <agent> --force replaces them.`
|
|
255
|
+
: "",
|
|
256
|
+
failed.length > 0 ? `${failed.length} could not be updated (${listed(failed)}); summer setup <agent> --force retries them.` : "",
|
|
257
|
+
"3.x tool names still run through summer_call; summer mcp --toolsets legacy lists them again.",
|
|
258
|
+
]
|
|
259
|
+
.filter(Boolean)
|
|
260
|
+
.join(" ");
|
|
261
|
+
}
|
|
@@ -79,9 +79,18 @@ export declare const SKILL_VERSION_MARKER_FILENAME = ".summer-version";
|
|
|
79
79
|
export interface SkillVersionMarker {
|
|
80
80
|
version: string;
|
|
81
81
|
installedAt: string;
|
|
82
|
+
/** Summer skills the upgrade refresh left because someone changed them (installer/skills-refresh.ts). */
|
|
83
|
+
kept?: string[];
|
|
82
84
|
}
|
|
83
85
|
export declare function readSkillMarker(dir: string): SkillVersionMarker | null;
|
|
84
|
-
|
|
86
|
+
/** The skills the upgrade refresh left in this folder (the raw marker; readSkillMarker keeps setup's fields). */
|
|
87
|
+
export declare function readKeptSkills(dir: string): string[];
|
|
88
|
+
/**
|
|
89
|
+
* Write the folder's marker. The skills an upgrade refresh left stay listed
|
|
90
|
+
* (so later refreshes keep leaving them), except the ones this install just
|
|
91
|
+
* wrote: `installed` names them; leave it out to keep every one.
|
|
92
|
+
*/
|
|
93
|
+
export declare function writeSkillMarker(dir: string, version: string, now?: Date, installed?: readonly string[]): string;
|
|
85
94
|
/**
|
|
86
95
|
* Candidate locations for skill-version markers. Listed roughly in order of
|
|
87
96
|
* how likely a Summer user is to have them populated. We probe every candidate,
|
|
@@ -108,6 +117,10 @@ export interface RecordedInstall {
|
|
|
108
117
|
/** Where the answer came from: the agent's config file, or — when it has
|
|
109
118
|
* no readable Summer entry — the nature of the CLI running this check. */
|
|
110
119
|
source: "agent-config" | "running-cli";
|
|
120
|
+
/** The agent's config lists no summer-engine server in any shape (`setup
|
|
121
|
+
* --cli`, or skills installed alone) and `setup --cli` can set the agent
|
|
122
|
+
* up: the refresh keeps it without an MCP server. */
|
|
123
|
+
cliOnly?: boolean;
|
|
111
124
|
}
|
|
112
125
|
/** An npm dist-tag (`latest`, `next`) or an exact version (`3.9.1`). */
|
|
113
126
|
export type RefreshChannel = string;
|
|
@@ -153,7 +166,8 @@ export declare function buildSkillsVersionCheck(input: SkillsVersionCheckInput):
|
|
|
153
166
|
* The command that refreshes an agent's skills without changing how its MCP
|
|
154
167
|
* entry was recorded: the local-dev form re-links the same checkout
|
|
155
168
|
* (`setup --local-dev --force` re-copies the checkout's skills); the npx form
|
|
156
|
-
* is for installs that already run the published package.
|
|
169
|
+
* is for installs that already run the published package. An agent with no
|
|
170
|
+
* summer-engine entry refreshes with `--cli`, so the refresh adds no MCP server.
|
|
157
171
|
*/
|
|
158
172
|
export declare function skillsRefreshCommand(agent: string, install: RecordedInstall | null, channel?: RefreshChannel): string;
|
|
159
173
|
/** The shape `summer setup --local-dev` writes: `node <…>/dist/bin/summer.js mcp`
|
|
@@ -173,6 +187,12 @@ export declare function isLocalDevServerConfig(server: StdioMcpServerConfig): bo
|
|
|
173
187
|
* Returns null when the file is missing, unparsable, or has no Summer entry.
|
|
174
188
|
*/
|
|
175
189
|
export declare function readRecordedMcpServer(text: string, format: McpFormat): StdioMcpServerConfig | null;
|
|
190
|
+
/**
|
|
191
|
+
* Does the agent's config list a summer-engine server in any shape: the stdio
|
|
192
|
+
* entry setup writes, a URL entry under that name, or (Claude Code) a
|
|
193
|
+
* per-project entry under projects.<dir>.mcpServers?
|
|
194
|
+
*/
|
|
195
|
+
export declare function listsSummerEngine(text: string, format: McpFormat): boolean;
|
|
176
196
|
/** Is the CLI running this check a source checkout rather than an installed
|
|
177
197
|
* package? npm/npx/global installs all live under a node_modules directory. */
|
|
178
198
|
export declare function runningCliIsCheckout(packageRoot?: string): boolean;
|