summer-engine 4.0.0 → 4.2.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 +1 -1
- package/CHANGELOG.md +37 -0
- package/README.md +20 -9
- 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/memory.js +6 -2
- package/dist/cli/commands/ops.d.ts +20 -3
- package/dist/cli/commands/ops.js +55 -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/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/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 +4 -0
- package/dist/core/ops/catalog.js +13 -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/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 +32 -3
- 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 -0
- 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
package/dist/cli/commands/ops.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { CLI_AREAS } from "../../core/ops/areas.js";
|
|
2
3
|
import { localEntries, localInputSchema, localWireForm, missingInput } from "../../core/ops/catalog.js";
|
|
3
|
-
import { answerOfHosted, callHostedV4, hostedCallFailure, searchEverywhere } from "../../core/ops/cloud.js";
|
|
4
|
-
import { errorAnswer, isLocalOp, localToolsFor, opFromCli, OP_NAME } from "../../core/ops/contract.js";
|
|
4
|
+
import { answerOfHosted, callHostedV4, describeFromShipped, hostedCallFailure, searchEverywhere } from "../../core/ops/cloud.js";
|
|
5
|
+
import { errorAnswer, isLocalOp, localToolsFor, opFromCli, OP_NAME, resolveOpName } from "../../core/ops/contract.js";
|
|
5
6
|
import { ENGINE_SPLIT_OPS, engineOpDef, splitToolArgs } from "../../core/ops/engine.js";
|
|
6
7
|
import { engineFailure, engineNotRunning } from "../../core/ops/engine-answer.js";
|
|
7
8
|
import { loadLibraryIndex } from "../../core/library-search.js";
|
|
@@ -18,12 +19,7 @@ import { dispatchTool, EngineUnavailableError, ToolResultError } from "../../cor
|
|
|
18
19
|
* `summer tool` runs); cloud operations run on the hosted Summer server
|
|
19
20
|
* through its summer_call, with the person's sign-in.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
export const CLI_AREAS = [
|
|
23
|
-
"team", "profile", "docs", "assets", "makers", "jobs", "projects", "plans", "games", "store", "jams", "pricing",
|
|
24
|
-
"analytics", "experiments", "marketing", "players", "economy", "inbox", "apps", "studio", "chats", "stories",
|
|
25
|
-
"plugins", "engine", "account",
|
|
26
|
-
];
|
|
22
|
+
export { CLI_AREAS };
|
|
27
23
|
const RESERVED_FLAGS = new Set(["json", "dry-run", "wait", "idempotency-key", "input"]);
|
|
28
24
|
const camel = (flag) => flag.replace(/-([a-z0-9])/g, (_match, letter) => letter.toUpperCase());
|
|
29
25
|
/** A flag's field: camelCase (`--scene-path` is scenePath), or snake_case when the schema names it so (`--look-at` is look_at). */
|
|
@@ -95,6 +91,9 @@ export function parseCallFlags(argv, schema) {
|
|
|
95
91
|
}
|
|
96
92
|
}
|
|
97
93
|
out.input = { ...out.input, ...fields };
|
|
94
|
+
// --dry-run anywhere makes a dry run, whatever the input says: allow rules for dry runs rely on it (installer/cli-permissions.ts).
|
|
95
|
+
if (out.dryRun)
|
|
96
|
+
delete out.input.dryRun;
|
|
98
97
|
return out;
|
|
99
98
|
}
|
|
100
99
|
export const defaultOpsCliDeps = {
|
|
@@ -115,18 +114,21 @@ async function runLocal(op, call, deps) {
|
|
|
115
114
|
const tool = split?.tool ?? entry?.mcpTool ?? localToolsFor(op)[0];
|
|
116
115
|
if (!tool)
|
|
117
116
|
return errorAnswer("not_found", `No operation is named "${op}".`, { next: { op: "ops.search", input: { query: op } } });
|
|
118
|
-
|
|
117
|
+
// maxCharacters cuts the text an MCP client gets; the CLI prints the whole answer.
|
|
118
|
+
const { maxCharacters, ...input } = call.input;
|
|
119
|
+
const uncut = maxCharacters === undefined ? undefined : ["maxCharacters: the CLI prints the whole answer; cut it in the shell (for example | head -c 8000)."];
|
|
120
|
+
const args = split ? splitToolArgs(split, input) : input;
|
|
119
121
|
const def = engineOpDef(op);
|
|
120
122
|
const guide = guideOf(tool);
|
|
121
123
|
const describe = guide && def ? { op: "docs.read", input: { id: guide } } : { op: "ops.describe", input: { name: op } };
|
|
122
|
-
const missing = missingInput(localInputSchema(op),
|
|
124
|
+
const missing = missingInput(localInputSchema(op), input);
|
|
123
125
|
if (missing)
|
|
124
126
|
return errorAnswer("invalid_input", missing, { next: describe });
|
|
125
127
|
if (call.dryRun) {
|
|
126
128
|
return { ok: true, status: "dry_run", preview: { summary: `${entry?.title ?? op}: the input is valid. Nothing was done.`, changes: [], approval: entry?.approval ?? "none", estimate: null, valid: true } };
|
|
127
129
|
}
|
|
128
130
|
try {
|
|
129
|
-
return { ok: true, status: "done", result: await deps.dispatch(tool, args) };
|
|
131
|
+
return { ok: true, status: "done", result: await deps.dispatch(tool, args), ...(uncut ? { warnings: uncut } : {}) };
|
|
130
132
|
}
|
|
131
133
|
catch (error) {
|
|
132
134
|
if (error instanceof EngineUnavailableError)
|
|
@@ -249,6 +251,44 @@ export async function runOpFromCli(op, argv, deps = defaultOpsCliDeps) {
|
|
|
249
251
|
deps.printError(`Warning: ${warning}`);
|
|
250
252
|
return answer.ok ? 0 : 1;
|
|
251
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* `summer ops search <words...>`: every operation, local and cloud, signed in
|
|
256
|
+
* or not; a cloud one that needs a sign-in says so. A how-to (docs.search
|
|
257
|
+
* first) points at the library.
|
|
258
|
+
*/
|
|
259
|
+
export async function searchFromCli(query, options, deps = defaultOpsCliDeps) {
|
|
260
|
+
const { items, warning } = await searchEverywhere(query, { area: options.area, limit: options.limit }, deps.hosted);
|
|
261
|
+
if (options.json) {
|
|
262
|
+
deps.print(JSON.stringify({ ok: true, status: "done", result: { items }, ...(warning ? { warnings: [warning] } : {}) }));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const item of items)
|
|
266
|
+
deps.print(`${item.name.padEnd(32)} ${item.effect.padEnd(13)} ${item.title}${item.needsSignIn ? " (needs sign-in)" : ""}`);
|
|
267
|
+
if (items[0]?.name === "docs.search")
|
|
268
|
+
deps.print(`How-to guides for this: summer docs search --query ${shellWord(query)}`);
|
|
269
|
+
if (warning)
|
|
270
|
+
deps.printError(warning);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* `summer ops describe <op>`: a local operation from this package; a cloud
|
|
274
|
+
* one from Summer's server, or from the shipped catalog when the server
|
|
275
|
+
* cannot answer (signed out, offline).
|
|
276
|
+
*/
|
|
277
|
+
export async function describeFromCli(op, deps = defaultOpsCliDeps, options = {}) {
|
|
278
|
+
const local = localWireForm(resolveOpName(op));
|
|
279
|
+
if (local) {
|
|
280
|
+
// --json: the same answer shape as the cloud describe ({operation}).
|
|
281
|
+
deps.print(options.json ? JSON.stringify({ ok: true, status: "done", result: { operation: local } }) : JSON.stringify(local, null, 2));
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
const asked = await runCloud("ops.describe", { input: { name: op }, json: options.json === true, dryRun: false, wait: false }, deps);
|
|
285
|
+
const answer = describeFromShipped(op, asked) ?? asked;
|
|
286
|
+
(answer.ok ? deps.print : deps.printError)(options.json ? JSON.stringify(answer) : formatAnswer(answer));
|
|
287
|
+
if (!options.json && answer.ok)
|
|
288
|
+
for (const warning of answer.warnings ?? [])
|
|
289
|
+
deps.printError(`Warning: ${warning}`);
|
|
290
|
+
return answer.ok ? 0 : 1;
|
|
291
|
+
}
|
|
252
292
|
function areaCommand(area) {
|
|
253
293
|
return new Command(area)
|
|
254
294
|
.description(`Summer ${area} operations: summer ${area} <action> --field value. Find them: summer ops search ${area}`)
|
|
@@ -276,26 +316,14 @@ export function opsCliCommands() {
|
|
|
276
316
|
.option("--limit <n>", "Most results", "8")
|
|
277
317
|
.option("--json", "Print the answer as JSON")
|
|
278
318
|
.action(async (wordsArg, options) => {
|
|
279
|
-
|
|
280
|
-
if (options.json) {
|
|
281
|
-
defaultOpsCliDeps.print(JSON.stringify({ ok: true, status: "done", result: { items }, ...(warning ? { warnings: [warning] } : {}) }));
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
for (const item of items)
|
|
285
|
-
defaultOpsCliDeps.print(`${item.name.padEnd(32)} ${item.effect.padEnd(13)} ${item.title}`);
|
|
286
|
-
if (warning)
|
|
287
|
-
defaultOpsCliDeps.printError(warning);
|
|
319
|
+
await searchFromCli(wordsArg.join(" "), { area: options.area, limit: Number(options.limit) || 8, json: options.json === true });
|
|
288
320
|
});
|
|
289
321
|
ops
|
|
290
322
|
.command("describe <op>")
|
|
291
323
|
.description("Show one operation with its exact input")
|
|
292
|
-
.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
defaultOpsCliDeps.print(JSON.stringify(local, null, 2));
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
process.exitCode = await runOpFromCli("ops.describe", ["--name", op]);
|
|
324
|
+
.option("--json", "Print the answer as JSON")
|
|
325
|
+
.action(async (op, options) => {
|
|
326
|
+
process.exitCode = await describeFromCli(op, defaultOpsCliDeps, { json: options.json === true });
|
|
299
327
|
});
|
|
300
328
|
return [whoami, ops, ...CLI_AREAS.map(areaCommand)];
|
|
301
329
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare const setupCommand: Command;
|
|
3
|
+
/** True when `path` sits in a git repository that does not ignore it (false outside git, or without git). */
|
|
4
|
+
export declare function notGitIgnored(path: string, cwd?: string): boolean;
|
|
3
5
|
/** One line instead of a row per three skills: the first few names and how many more. `--json` keeps the full list. */
|
|
4
6
|
export declare function skillNamesSample(names: readonly string[], max?: number): string;
|
|
@@ -1,19 +1,31 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { relative } from "path";
|
|
1
4
|
import { Command, Option } from "commander";
|
|
2
5
|
import { configureAgentMcp, configureSummerGamesMcp, resolveAgentSelection, resolveConfigScope, supportedAgents, } from "../../installer/agent-config.js";
|
|
3
6
|
import { previewNote, setupSkills } from "../../installer/setup.js";
|
|
4
7
|
import { printDoctorResult, runDoctor } from "../../core/capabilities/doctor.js";
|
|
5
8
|
import { brandLine, c, sym, tildeify } from "../../core/format.js";
|
|
6
9
|
import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js";
|
|
7
|
-
import { resolveDefaultChannel } from "../../installer/version-check.js";
|
|
8
|
-
import { agentLabel } from "../../installer/agent-table.js";
|
|
10
|
+
import { listsSummerEngine, resolveDefaultChannel } from "../../installer/version-check.js";
|
|
11
|
+
import { agentLabel, agentSpec, cliSetupRefusal, defaultPathContext } from "../../installer/agent-table.js";
|
|
12
|
+
import { addAllowRules, allowRulesPath, removeAllowRules, supportsAllowRules } from "../../installer/cli-permissions.js";
|
|
9
13
|
import { PROFILES, parseProfile } from "../../core/tool-profiles.js";
|
|
14
|
+
import { findSetupProjectRoot, writeAgentInstructions } from "../../project-memory/agent-instructions.js";
|
|
10
15
|
const PROFILE_NOTES = {
|
|
11
16
|
full: "Every Summer tool.",
|
|
12
17
|
engine: "Engine, generation and store tools. Every tool: summer setup <agent> --profile full",
|
|
13
18
|
assets: "Generation and asset tools, no engine tools. Every tool: summer setup <agent> --profile full",
|
|
14
19
|
};
|
|
20
|
+
/** The options that only shape the MCP entry, which --cli does not write. */
|
|
21
|
+
const MCP_ENTRY_FLAGS = [
|
|
22
|
+
["print", "--print"],
|
|
23
|
+
["localDev", "--local-dev"],
|
|
24
|
+
["channel", "--channel"],
|
|
25
|
+
["profile", "--profile"],
|
|
26
|
+
];
|
|
15
27
|
export const setupCommand = new Command("setup")
|
|
16
|
-
.description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games).")
|
|
28
|
+
.description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games). --cli installs the skills only: the agent runs summer commands in its shell, with no MCP server.")
|
|
17
29
|
.argument("[agent]", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
18
30
|
.option("--agent <agent>", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
19
31
|
.option("--scope <scope>", "Configuration scope: user or project", "user")
|
|
@@ -25,6 +37,11 @@ export const setupCommand = new Command("setup")
|
|
|
25
37
|
.option("--no-games", "Do not add the Summer Games MCP (summer-games: friends, parties and messages as you, signed in through summer.games)")
|
|
26
38
|
.option("--profile <profile>", "MCP tool profile: full (every tool, the default), engine (engine, generation and the store; under 128 tools; the default for VS Code Copilot), assets (generation and the asset library, no engine tools)")
|
|
27
39
|
.option("--assets-only", "Set up for making assets without Summer Engine: the assets profile, only the asset skills, and no Summer Games")
|
|
40
|
+
.option("--cli", "CLI only: install the skills and no MCP server (and no Summer Games). The agent runs Summer as summer <area> <action> commands in its shell; the summer-cli skill teaches it")
|
|
41
|
+
.option("--no-mcp", "Same as --cli")
|
|
42
|
+
.option("--allow-rules", "With --cli, for Claude Code and Codex: let the agent run read-only summer commands, and dry runs of spends, without asking. Prints every rule; written only with --yes")
|
|
43
|
+
.option("--remove-allow-rules", "Remove the allow rules --allow-rules added (Claude Code, Codex), and nothing else")
|
|
44
|
+
.option("--no-project-files", "Inside a Summer project, do not add the Summer instructions to its AGENTS.md and CLAUDE.md (added with --yes by default)")
|
|
28
45
|
.option("--json", "Print setup result as JSON")
|
|
29
46
|
.option("--force", "Overwrite existing skill content (passes --force through to skills install)")
|
|
30
47
|
.option("--recommended", "Install only the recommended skill subset instead of the whole library")
|
|
@@ -34,6 +51,17 @@ export const setupCommand = new Command("setup")
|
|
|
34
51
|
.action(async (agentArg, opts) => {
|
|
35
52
|
const agent = resolveAgentSelection(agentArg, opts.agent);
|
|
36
53
|
const scope = resolveConfigScope(opts.scope);
|
|
54
|
+
if (opts.removeAllowRules) {
|
|
55
|
+
await removeCliAllowRules(agent, scope, opts);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (opts.cli || opts.mcp === false) {
|
|
59
|
+
await setupCliOnly(agent, scope, opts);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// On stderr, so --json output stays one JSON document.
|
|
63
|
+
if (opts.allowRules)
|
|
64
|
+
console.error(` ${sym.warn()} ${c.yellow("--allow-rules works with --cli only (the MCP server asks through the agent's own tool approvals); no rules were added.")}`);
|
|
37
65
|
const profile = resolveSetupProfile(opts);
|
|
38
66
|
const defaultChannel = await resolveDefaultChannel(cliVersion);
|
|
39
67
|
const config = await configureAgentMcp({
|
|
@@ -60,6 +88,7 @@ export const setupCommand = new Command("setup")
|
|
|
60
88
|
stableOnly: Boolean(opts.stableOnly),
|
|
61
89
|
assetsOnly: Boolean(opts.assetsOnly),
|
|
62
90
|
});
|
|
91
|
+
const projectFiles = setupProjectInstructions(opts);
|
|
63
92
|
const doctor = await runDoctor({ quiet: true });
|
|
64
93
|
if (opts.json) {
|
|
65
94
|
console.log(JSON.stringify({
|
|
@@ -67,16 +96,184 @@ export const setupCommand = new Command("setup")
|
|
|
67
96
|
config,
|
|
68
97
|
games,
|
|
69
98
|
skills,
|
|
99
|
+
projectFiles,
|
|
70
100
|
doctor,
|
|
71
101
|
}, null, 2));
|
|
72
102
|
}
|
|
73
103
|
else {
|
|
74
|
-
printSetupResult(config, games, skills, doctor);
|
|
104
|
+
printSetupResult(config, games, skills, doctor, projectFiles);
|
|
75
105
|
}
|
|
76
106
|
if (!doctor.ok || skills.status === "failed") {
|
|
77
107
|
process.exit(1);
|
|
78
108
|
}
|
|
79
109
|
});
|
|
110
|
+
/**
|
|
111
|
+
* `summer setup <agent> --cli`: the skills (the asset skills with --assets-only)
|
|
112
|
+
* and no MCP server, so the agent runs Summer as `summer <area> <action>` in its
|
|
113
|
+
* shell. Writes no agent config file; an existing summer-engine entry from an
|
|
114
|
+
* earlier setup is left as it is and named in a warning.
|
|
115
|
+
*/
|
|
116
|
+
async function setupCliOnly(agent, scope, opts) {
|
|
117
|
+
const spec = agentSpec(agent);
|
|
118
|
+
const mcpFlags = MCP_ENTRY_FLAGS.filter(([key]) => opts[key] !== undefined && opts[key] !== false).map(([, flag]) => flag);
|
|
119
|
+
if (mcpFlags.length > 0) {
|
|
120
|
+
throw new Error(`${mcpFlags.join(", ")} sets up the MCP entry, and --cli writes none. Leave ${mcpFlags.length === 1 ? "it" : "them"} out, or leave out --cli.`);
|
|
121
|
+
}
|
|
122
|
+
const refusal = cliSetupRefusal(spec);
|
|
123
|
+
if (refusal)
|
|
124
|
+
throw new Error(refusal);
|
|
125
|
+
const warnings = spec.status === "legacy" && spec.legacyNote ? [spec.legacyNote] : [];
|
|
126
|
+
const existing = await recordedMcpEntry(agent, scope);
|
|
127
|
+
if (existing) {
|
|
128
|
+
warnings.push(`${tildeify(existing)} still lists the summer-engine MCP server from an earlier setup. Remove that entry to use the CLI only.`);
|
|
129
|
+
}
|
|
130
|
+
const dryRun = Boolean(opts.dryRun);
|
|
131
|
+
const skills = setupSkills(agent, {
|
|
132
|
+
dryRun,
|
|
133
|
+
yes: Boolean(opts.yes),
|
|
134
|
+
force: Boolean(opts.force),
|
|
135
|
+
scope,
|
|
136
|
+
recommended: Boolean(opts.recommended),
|
|
137
|
+
stableOnly: Boolean(opts.stableOnly),
|
|
138
|
+
assetsOnly: Boolean(opts.assetsOnly),
|
|
139
|
+
commands: false,
|
|
140
|
+
});
|
|
141
|
+
// Read-only and dry-run summer commands run without asking, where the agent has clean allow rules.
|
|
142
|
+
// Opt-in: the flag is in the command the person approves.
|
|
143
|
+
let allowRules = null;
|
|
144
|
+
if (opts.allowRules && !supportsAllowRules(agent)) {
|
|
145
|
+
warnings.push(`--allow-rules is for Claude Code and Codex; ${agentLabel(agent)} manages command approvals in its own settings.`);
|
|
146
|
+
}
|
|
147
|
+
else if (opts.allowRules) {
|
|
148
|
+
try {
|
|
149
|
+
allowRules = await addAllowRules(agent, allowRulesPath(agent, scope, defaultPathContext()), Boolean(opts.yes) && !dryRun);
|
|
150
|
+
const shared = agent === "claude-code" && scope === "project" ? notGitIgnored(allowRules.path) : false;
|
|
151
|
+
if (shared)
|
|
152
|
+
warnings.push(`${tildeify(allowRules.path)} is not in .gitignore, so git would share these personal allow rules. Add .claude/settings.local.json to .gitignore.`);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
warnings.push(error instanceof Error ? error.message : String(error));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const projectFiles = setupProjectInstructions(opts);
|
|
159
|
+
const doctor = await runDoctor({ quiet: true });
|
|
160
|
+
const ok = doctor.ok && skills.status !== "failed";
|
|
161
|
+
if (opts.json) {
|
|
162
|
+
console.log(JSON.stringify({ ok, mcp: false, agent, scope, config: null, games: null, existingMcpEntry: existing, skills, allowRules, projectFiles, doctor, warnings }, null, 2));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const label = agentLabel(agent);
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log(brandLine(cliVersion));
|
|
168
|
+
console.log("");
|
|
169
|
+
console.log(` ${dryRun ? c.dim("(dry run)") : sym.ok()} ${c.bold(`CLI only for ${label}`)}: no MCP server`);
|
|
170
|
+
console.log(` ${c.dim("The agent runs Summer in its shell: summer <area> <action>, or npx -y summer-engine@latest <area> <action>. The summer-cli skill teaches it.")}`);
|
|
171
|
+
printSkillsLine(skills, dryRun);
|
|
172
|
+
if (allowRules)
|
|
173
|
+
printAllowRules(allowRules, label, Boolean(opts.yes) && !dryRun);
|
|
174
|
+
else if (supportsAllowRules(agent) && !opts.allowRules) {
|
|
175
|
+
console.log(` ${sym.arrow()} ${c.dim(`${label} asks before each summer command. To run read-only and dry-run ones without asking: summer setup ${agent} --cli --allow-rules --yes (it prints every rule).`)}`);
|
|
176
|
+
}
|
|
177
|
+
printProjectFiles(projectFiles);
|
|
178
|
+
for (const warning of warnings)
|
|
179
|
+
console.log(` ${sym.warn()} ${c.yellow(warning)}`);
|
|
180
|
+
console.log("");
|
|
181
|
+
printDoctorResult(doctor);
|
|
182
|
+
if (ok && !dryRun)
|
|
183
|
+
printTryIt(label, Boolean(opts.assetsOnly));
|
|
184
|
+
}
|
|
185
|
+
if (!ok)
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
/** The allow rules, each on its line: what the agent now runs without asking, or would with --yes. */
|
|
189
|
+
function printAllowRules(result, label, write) {
|
|
190
|
+
const where = c.dim(tildeify(result.path));
|
|
191
|
+
if (!write) {
|
|
192
|
+
console.log(` ${sym.arrow()} ${label} would run these summer commands without asking. Add them: run the same command with --yes. ${where}`);
|
|
193
|
+
}
|
|
194
|
+
else if (result.changed.length === 0) {
|
|
195
|
+
console.log(` ${sym.ok()} ${label} already runs read-only and dry-run summer commands without asking ${where}`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(` ${sym.ok()} ${label} runs these summer commands without asking (${result.changed.length} rules added) ${where}`);
|
|
200
|
+
}
|
|
201
|
+
for (const rule of result.rules)
|
|
202
|
+
console.log(` ${c.dim(rule)}`);
|
|
203
|
+
console.log(` ${c.dim("Changes, publishing, prices, deletes and messages still ask. jobs wait and assets get save the files they name. Put --dry-run right after the action: summer assets generate-image --dry-run --prompt ... Undo: --remove-allow-rules")}`);
|
|
204
|
+
}
|
|
205
|
+
/** True when `path` sits in a git repository that does not ignore it (false outside git, or without git). */
|
|
206
|
+
export function notGitIgnored(path, cwd = process.cwd()) {
|
|
207
|
+
const run = (args) => spawnSync("git", args, { cwd, stdio: "ignore" }).status;
|
|
208
|
+
if (run(["rev-parse", "--is-inside-work-tree"]) !== 0)
|
|
209
|
+
return false;
|
|
210
|
+
return run(["check-ignore", "-q", relative(cwd, path)]) === 1;
|
|
211
|
+
}
|
|
212
|
+
/** `summer setup <agent> --remove-allow-rules`: take out exactly the rules --allow-rules added. */
|
|
213
|
+
async function removeCliAllowRules(agent, scope, opts) {
|
|
214
|
+
if (!supportsAllowRules(agent))
|
|
215
|
+
throw new Error(`--remove-allow-rules is for Claude Code and Codex; Summer adds no allow rules for ${agentLabel(agent)}.`);
|
|
216
|
+
const result = await removeAllowRules(agent, allowRulesPath(agent, scope, defaultPathContext()), !opts.dryRun);
|
|
217
|
+
if (opts.json) {
|
|
218
|
+
console.log(JSON.stringify({ ok: true, agent, scope, allowRules: result }, null, 2));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const where = c.dim(tildeify(result.path));
|
|
222
|
+
if (result.changed.length === 0)
|
|
223
|
+
console.log(` ${sym.ok()} No Summer allow rules for ${agentLabel(agent)} ${where}`);
|
|
224
|
+
else
|
|
225
|
+
console.log(` ${opts.dryRun ? c.dim("(dry run)") : sym.ok()} ${opts.dryRun ? "Would remove" : "Removed"} ${result.changed.length} Summer allow rules for ${agentLabel(agent)} ${where}`);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Inside a Summer project (findSetupProjectRoot: project.godot at or above the
|
|
229
|
+
* working directory, no higher than the git top level, never home): its
|
|
230
|
+
* AGENTS.md and CLAUDE.md, added when missing and never overwritten
|
|
231
|
+
* (project-memory/agent-instructions.ts). Like the skills, they are only
|
|
232
|
+
* planned without --yes. Null outside a project, with --no-project-files,
|
|
233
|
+
* --print, and --assets-only (the block is about building in Summer Engine).
|
|
234
|
+
*/
|
|
235
|
+
function setupProjectInstructions(opts) {
|
|
236
|
+
if (opts.projectFiles === false || opts.print || opts.assetsOnly)
|
|
237
|
+
return null;
|
|
238
|
+
const root = findSetupProjectRoot();
|
|
239
|
+
if (!root)
|
|
240
|
+
return null;
|
|
241
|
+
const dryRun = Boolean(opts.dryRun);
|
|
242
|
+
const written = Boolean(opts.yes) && !dryRun;
|
|
243
|
+
return { root, written, dryRun, files: writeAgentInstructions(root, { dryRun: !written }) };
|
|
244
|
+
}
|
|
245
|
+
function printProjectFiles(project) {
|
|
246
|
+
if (!project)
|
|
247
|
+
return;
|
|
248
|
+
const changed = project.files.filter((file) => file.action === "created" || file.action === "added" || file.action === "updated");
|
|
249
|
+
if (changed.length) {
|
|
250
|
+
const where = c.dim(changed.map((file) => tildeify(file.path)).join(", "));
|
|
251
|
+
if (project.written)
|
|
252
|
+
console.log(` ${sym.ok()} Added the Summer instructions for agents ${where}`);
|
|
253
|
+
else if (project.dryRun)
|
|
254
|
+
console.log(` ${c.dim("(dry run)")} Would add the Summer instructions for agents ${where}`);
|
|
255
|
+
else
|
|
256
|
+
console.log(` ${sym.warn()} Instructions for agents not added. Run the same setup command again with --yes to add them ${where}`);
|
|
257
|
+
}
|
|
258
|
+
for (const file of project.files) {
|
|
259
|
+
if (!file.reason)
|
|
260
|
+
continue;
|
|
261
|
+
const what = file.action === "skipped" ? "not written" : "left as it is";
|
|
262
|
+
console.log(` ${sym.warn()} ${c.yellow(`${tildeify(file.path)} ${what}: ${file.reason}`)}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** The config file that already lists summer-engine for this agent and scope, or null. */
|
|
266
|
+
async function recordedMcpEntry(agent, scope) {
|
|
267
|
+
try {
|
|
268
|
+
const target = await configureAgentMcp({ agent, scope, print: true });
|
|
269
|
+
if (!existsSync(target.path))
|
|
270
|
+
return null;
|
|
271
|
+
return listsSummerEngine(readFileSync(target.path, "utf-8"), target.format) ? target.path : null;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
80
277
|
/** --assets-only means the assets profile; --profile must name a profile and agree with it. */
|
|
81
278
|
function resolveSetupProfile(opts) {
|
|
82
279
|
const profile = opts.profile === undefined ? undefined : parseProfile(opts.profile);
|
|
@@ -90,7 +287,7 @@ function resolveSetupProfile(opts) {
|
|
|
90
287
|
}
|
|
91
288
|
return profile;
|
|
92
289
|
}
|
|
93
|
-
function printSetupResult(config, games, skills, doctor) {
|
|
290
|
+
function printSetupResult(config, games, skills, doctor, projectFiles) {
|
|
94
291
|
if (config.print) {
|
|
95
292
|
console.log(config.snippet.trimEnd());
|
|
96
293
|
return;
|
|
@@ -132,6 +329,22 @@ function printSetupResult(config, games, skills, doctor) {
|
|
|
132
329
|
if (!config.dryRun)
|
|
133
330
|
console.log(` ${c.dim(`Sign in once: ${games.signIn}`)}`);
|
|
134
331
|
}
|
|
332
|
+
printSkillsLine(skills, config.dryRun);
|
|
333
|
+
printProjectFiles(projectFiles);
|
|
334
|
+
for (const warning of config.warnings) {
|
|
335
|
+
console.log(` ${sym.warn()} ${c.yellow(warning)}`);
|
|
336
|
+
}
|
|
337
|
+
console.log("");
|
|
338
|
+
printDoctorResult(doctor);
|
|
339
|
+
if (doctor.ok && skills.status !== "failed" && !config.dryRun)
|
|
340
|
+
printTryIt(label, config.profile === "assets");
|
|
341
|
+
}
|
|
342
|
+
function printTryIt(label, assets) {
|
|
343
|
+
console.log("");
|
|
344
|
+
const ask = assets ? "make a sword clash sound effect" : "add a DirectionalLight3D and Camera3D to my scene";
|
|
345
|
+
console.log(`${c.dim("Try it:")} open ${label} and ask: ${c.bold(`"${ask}"`)}`);
|
|
346
|
+
}
|
|
347
|
+
function printSkillsLine(skills, dryRun) {
|
|
135
348
|
if (skills.status === "installed") {
|
|
136
349
|
const installed = parseInstalledSkills(skills.stdout);
|
|
137
350
|
if (installed.length > 0) {
|
|
@@ -147,7 +360,7 @@ function printSetupResult(config, games, skills, doctor) {
|
|
|
147
360
|
console.log(` ${sym.ok()} ${skills.message}`);
|
|
148
361
|
}
|
|
149
362
|
}
|
|
150
|
-
else if (skills.status === "planned" &&
|
|
363
|
+
else if (skills.status === "planned" && dryRun) {
|
|
151
364
|
const note = previewNote(skills);
|
|
152
365
|
console.log(` ${c.dim("(dry run)")} Would install ${c.bold(String(skills.count ?? "?") + " skills")}${note ? c.dim(` (${note})`) : ""} ${c.dim(skills.destination ?? "")}`);
|
|
153
366
|
}
|
|
@@ -159,16 +372,6 @@ function printSetupResult(config, games, skills, doctor) {
|
|
|
159
372
|
if (skills.stderr)
|
|
160
373
|
console.log(` ${c.dim(skills.stderr)}`);
|
|
161
374
|
}
|
|
162
|
-
for (const warning of config.warnings) {
|
|
163
|
-
console.log(` ${sym.warn()} ${c.yellow(warning)}`);
|
|
164
|
-
}
|
|
165
|
-
console.log("");
|
|
166
|
-
printDoctorResult(doctor);
|
|
167
|
-
if (doctor.ok && skills.status !== "failed" && !config.dryRun) {
|
|
168
|
-
console.log("");
|
|
169
|
-
const ask = config.profile === "assets" ? "make a sword clash sound effect" : "add a DirectionalLight3D and Camera3D to my scene";
|
|
170
|
-
console.log(`${c.dim("Try it:")} open ${label} and ask: ${c.bold(`"${ask}"`)}`);
|
|
171
|
-
}
|
|
172
375
|
}
|
|
173
376
|
/** One line instead of a row per three skills: the first few names and how many more. `--json` keeps the full list. */
|
|
174
377
|
export function skillNamesSample(names, max = 8) {
|
|
@@ -234,8 +234,13 @@ skillsCommand
|
|
|
234
234
|
.command("list")
|
|
235
235
|
.description("List available skills")
|
|
236
236
|
.option("--by-domain", "Group skills by their primary domain (same grouping as library/skills/README.md)")
|
|
237
|
+
.option("--json", "Print the skills as JSON")
|
|
237
238
|
.action((opts) => {
|
|
238
239
|
const skills = getBuiltinSkills();
|
|
240
|
+
if (opts.json) {
|
|
241
|
+
console.log(JSON.stringify({ skills }));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
239
244
|
if (skills.length === 0) {
|
|
240
245
|
console.log("No skills found.");
|
|
241
246
|
return;
|
|
@@ -287,6 +292,7 @@ skillsCommand
|
|
|
287
292
|
// Pre-reversal opt-in, kept one release as a hidden no-op so old scripts still parse.
|
|
288
293
|
.addOption(new Option("--include-preview", "No-op: preview skills install by default").hideHelp())
|
|
289
294
|
.option("--force", "Overwrite existing skill files (wipe stale skill dirs before copying)")
|
|
295
|
+
.option("--no-commands", "Claude Code: skip the slash commands (/summer, /gameskill, /start), which start work through the MCP tools")
|
|
290
296
|
.action((name, opts) => {
|
|
291
297
|
const agent = orDie(() => resolveSkillAgent(opts));
|
|
292
298
|
const scope = orDie(() => resolveSkillScope(agent, opts));
|
|
@@ -344,15 +350,16 @@ skillsCommand
|
|
|
344
350
|
console.log(previewSkippedLine(previewSkipped));
|
|
345
351
|
// Claude Code: also install slash commands (`commands/*.md`)
|
|
346
352
|
// into `~/.claude/commands/`. Other agents don't have an equivalent today.
|
|
347
|
-
// The commands start and route game work in the engine
|
|
348
|
-
// skills (--assets, setup --assets-only)
|
|
349
|
-
|
|
353
|
+
// The commands start and route game work in the engine through the MCP
|
|
354
|
+
// tools, so the asset skills (--assets, setup --assets-only) and a setup
|
|
355
|
+
// without MCP (--no-commands, setup --cli) leave them out.
|
|
356
|
+
if (agent === "claude-code" && !opts.assets && opts.commands !== false) {
|
|
350
357
|
const commandResults = installClaudeCommands(scope, Boolean(opts.force));
|
|
351
358
|
for (const r of commandResults) {
|
|
352
359
|
console.log(` ${r.action} command ${r.name} -> ${r.path}`);
|
|
353
360
|
}
|
|
354
361
|
}
|
|
355
|
-
writeMarkerForLocation(location);
|
|
362
|
+
writeMarkerForLocation(location, skills.map((skill) => skill.name));
|
|
356
363
|
printInstallSummary(skills.length, agent, scope, location);
|
|
357
364
|
});
|
|
358
365
|
/**
|
|
@@ -390,12 +397,13 @@ function installClaudeCommands(scope, force) {
|
|
|
390
397
|
* `skills-version-stale` check can detect drift later. Devin Desktop (formerly Windsurf) writes a
|
|
391
398
|
* single rules file with no surrounding dir, so we skip it there.
|
|
392
399
|
*/
|
|
393
|
-
function writeMarkerForLocation(location) {
|
|
400
|
+
function writeMarkerForLocation(location, installed) {
|
|
394
401
|
if (location.kind === "windsurf-rule-file")
|
|
395
402
|
return;
|
|
396
403
|
try {
|
|
397
404
|
mkdirSync(location.path, { recursive: true });
|
|
398
|
-
|
|
405
|
+
// Skills the upgrade refresh left stay protected, unless this install just replaced them.
|
|
406
|
+
writeSkillMarker(location.path, cliVersion, new Date(), installed);
|
|
399
407
|
}
|
|
400
408
|
catch {
|
|
401
409
|
// Marker is purely informational. If the FS rejects it (e.g. permission
|
|
@@ -405,7 +413,8 @@ function writeMarkerForLocation(location) {
|
|
|
405
413
|
skillsCommand
|
|
406
414
|
.command("info <name>")
|
|
407
415
|
.description("Show skill description and preview")
|
|
408
|
-
.
|
|
416
|
+
.option("--json", "Print the skill and its full SKILL.md body as JSON")
|
|
417
|
+
.action((name, opts) => {
|
|
409
418
|
const meta = getSkillMeta(name);
|
|
410
419
|
if (!meta) {
|
|
411
420
|
console.error(`Unknown skill: ${name}`);
|
|
@@ -416,6 +425,10 @@ skillsCommand
|
|
|
416
425
|
die(`Skill files missing: ${name}`);
|
|
417
426
|
const skillPath = join(src, "SKILL.md");
|
|
418
427
|
const content = readFileSync(skillPath, "utf-8");
|
|
428
|
+
if (opts.json) {
|
|
429
|
+
console.log(JSON.stringify({ ...meta, file: skillPath, body: content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "") }));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
419
432
|
console.log(`\n${meta.name}`);
|
|
420
433
|
console.log("-".repeat(40));
|
|
421
434
|
console.log(meta.description);
|
|
@@ -1,3 +1,29 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare const statusCommand: Command;
|
|
3
|
+
/** What `summer status` reports; `--json` prints exactly this. */
|
|
4
|
+
export interface StatusReport {
|
|
5
|
+
auth: {
|
|
6
|
+
signedIn: false;
|
|
7
|
+
loginUrl: string;
|
|
8
|
+
} | {
|
|
9
|
+
signedIn: true;
|
|
10
|
+
source: "env";
|
|
11
|
+
} | {
|
|
12
|
+
signedIn: true;
|
|
13
|
+
source: "stored";
|
|
14
|
+
email?: string;
|
|
15
|
+
identityError?: string;
|
|
16
|
+
};
|
|
17
|
+
engine: {
|
|
18
|
+
state: "not_running";
|
|
19
|
+
} | {
|
|
20
|
+
state: "not_responding";
|
|
21
|
+
port: number;
|
|
22
|
+
} | {
|
|
23
|
+
state: "running";
|
|
24
|
+
port: number;
|
|
25
|
+
version: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare function readStatus(): Promise<StatusReport>;
|
|
3
29
|
export declare function runStatus(log: (message: string) => void): Promise<void>;
|
|
@@ -4,52 +4,67 @@ import { resolveGatewayUrl } from "../../core/config.js";
|
|
|
4
4
|
import { getApiToken, getApiPort, checkEngineHealth } from "../../core/engine.js";
|
|
5
5
|
export const statusCommand = new Command("status")
|
|
6
6
|
.description("Check Summer Engine status, connection, and auth state")
|
|
7
|
-
.
|
|
8
|
-
|
|
7
|
+
.option("--json", "Print the status as JSON")
|
|
8
|
+
.action(async (opts) => {
|
|
9
|
+
if (opts.json)
|
|
10
|
+
console.log(JSON.stringify(await readStatus()));
|
|
11
|
+
else
|
|
12
|
+
await runStatus(console.log);
|
|
9
13
|
});
|
|
14
|
+
export async function readStatus() {
|
|
15
|
+
const authToken = await getAuthToken();
|
|
16
|
+
// The env credential is what every gateway call uses; a stored identity
|
|
17
|
+
// (if any) is not the one in effect, so it is not reported.
|
|
18
|
+
const auth = !authToken
|
|
19
|
+
? { signedIn: false, loginUrl: await loginPageHint() }
|
|
20
|
+
: hasEnvAuthToken()
|
|
21
|
+
? { signedIn: true, source: "env" }
|
|
22
|
+
: { signedIn: true, source: "stored", ...(await storedIdentity()) };
|
|
23
|
+
if (!(await getApiToken()))
|
|
24
|
+
return { auth, engine: { state: "not_running" } };
|
|
25
|
+
const port = await getApiPort();
|
|
26
|
+
const health = await checkEngineHealth(port);
|
|
27
|
+
// /api/health carries no project name/path/scene (see EngineHealth in
|
|
28
|
+
// core/engine.ts); summer_get_project_context is the read for those.
|
|
29
|
+
return { auth, engine: health ? { state: "running", port, version: health.version } : { state: "not_responding", port } };
|
|
30
|
+
}
|
|
10
31
|
export async function runStatus(log) {
|
|
32
|
+
const { auth, engine } = await readStatus();
|
|
11
33
|
log("Summer Engine Status\n");
|
|
12
|
-
|
|
13
|
-
if (!authToken) {
|
|
34
|
+
if (!auth.signedIn) {
|
|
14
35
|
log(" Auth: Not logged in");
|
|
15
36
|
log(" Run: npx -y summer-engine@latest login");
|
|
16
|
-
log(` Or: ${
|
|
37
|
+
log(` Or: ${auth.loginUrl}`);
|
|
17
38
|
}
|
|
18
|
-
else if (
|
|
19
|
-
// The env credential is what every gateway call uses; a stored identity
|
|
20
|
-
// (if any) is not the one in effect, so it is not printed.
|
|
39
|
+
else if (auth.source === "env") {
|
|
21
40
|
log(" Auth: SUMMER_TOKEN (env) — logout does not affect it");
|
|
22
41
|
}
|
|
23
42
|
else {
|
|
24
|
-
|
|
43
|
+
const identity = auth.identityError ? ` (identity unreadable: ${auth.identityError})` : auth.email ? ` as ${auth.email}` : "";
|
|
44
|
+
log(` Auth: Logged in${identity}`);
|
|
25
45
|
}
|
|
26
|
-
|
|
27
|
-
const port = await getApiPort();
|
|
28
|
-
if (!apiToken) {
|
|
46
|
+
if (engine.state === "not_running") {
|
|
29
47
|
log(" Engine: Not running (no api-token found)");
|
|
30
48
|
log("\n To start: summer run");
|
|
31
49
|
return;
|
|
32
50
|
}
|
|
33
51
|
log(" API Token: Found (~/.summer/api-token)");
|
|
34
|
-
log(` Port: ${port}`);
|
|
35
|
-
|
|
36
|
-
if (!health) {
|
|
52
|
+
log(` Port: ${engine.port}`);
|
|
53
|
+
if (engine.state === "not_responding") {
|
|
37
54
|
log(" Engine: Not responding (may have closed since last launch)");
|
|
38
55
|
log("\n To start: summer run");
|
|
39
56
|
return;
|
|
40
57
|
}
|
|
41
|
-
|
|
42
|
-
// core/engine.ts); summer_get_project_context is the read for those.
|
|
43
|
-
log(` Engine: Running (v${health.version})`);
|
|
58
|
+
log(` Engine: Running (v${engine.version})`);
|
|
44
59
|
}
|
|
45
60
|
/** A corrupt user.json must not take `summer status` down with it. */
|
|
46
|
-
async function
|
|
61
|
+
async function storedIdentity() {
|
|
47
62
|
try {
|
|
48
63
|
const userInfo = await getUserInfo();
|
|
49
|
-
return userInfo?.email ?
|
|
64
|
+
return userInfo?.email ? { email: userInfo.email } : {};
|
|
50
65
|
}
|
|
51
66
|
catch (error) {
|
|
52
|
-
return
|
|
67
|
+
return { identityError: error instanceof Error ? error.message : String(error) };
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
async function loginPageHint() {
|