@mkterswingman/5mghost-twinkler 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,13 @@ Lightweight AI runtime for the hosted Twinkler API.
4
4
 
5
5
  ```bash
6
6
  npm install -g @mkterswingman/5mghost-twinkler
7
- twinkler setup
8
7
  twinkler call GET /api/v1/channel/ibai/summary --query days=30
9
8
  ```
10
9
 
10
+ `npm install -g` installs bundled AI skills automatically for detected AI
11
+ clients. `twinkler setup` remains an idempotent repair command when a skill
12
+ target was unavailable during install.
13
+
11
14
  Auth uses the shared mkterswingman PAT. Do not commit `.env`, `auth.json`,
12
15
  PATs, bearer tokens, or any generated secret file to a remote repository. Enter
13
16
  PATs through `twinkler auth login --pat-stdin`; do not paste PATs into AI chat.
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, realpathSync } from "node:fs";
3
+ import { spawnSync } from "node:child_process";
3
4
  import { dirname, join } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { getAuthStatus, logout, PAT_LOGIN_URL, savePat } from "./auth.js";
@@ -17,6 +18,7 @@ const HELP = [
17
18
  " twinkler auth login --pat <TOKEN>",
18
19
  " twinkler auth login --pat-stdin",
19
20
  " twinkler call <GET|POST|DELETE> /api/v1/... [--query k=v] [--json '{...}']",
21
+ " twinkler update",
20
22
  " twinkler version",
21
23
  "",
22
24
  "mkterswingman PAT:",
@@ -72,6 +74,9 @@ export async function runCli(context = {}) {
72
74
  ].join("\n"));
73
75
  return results.some((result) => result.status === "error") ? 1 : 0;
74
76
  }
77
+ case "update":
78
+ case "upgrade":
79
+ return runUpdateCommand(args, { env, out, err });
75
80
  case "doctor": {
76
81
  const auth = getAuthStatus({ authJsonPath: paths.authJsonPath, env });
77
82
  out([
@@ -188,6 +193,30 @@ function renderSkillInstallResults(results) {
188
193
  })
189
194
  ].join("\n");
190
195
  }
196
+ function runUpdateCommand(args, context) {
197
+ const dryRun = args.includes("--dry-run") || context.env.TWINKLER_UPDATE_DRY_RUN === "1";
198
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
199
+ const installArgs = ["install", "-g", "@mkterswingman/5mghost-twinkler@latest"];
200
+ if (dryRun) {
201
+ context.out(`Update command: ${npmCommand} ${installArgs.join(" ")}`);
202
+ return 0;
203
+ }
204
+ context.out("Updating Twinkler helper...");
205
+ const result = spawnSync(npmCommand, installArgs, {
206
+ stdio: "inherit",
207
+ env: context.env
208
+ });
209
+ if (result.error) {
210
+ context.err(`Update failed: ${result.error.message}`);
211
+ return 1;
212
+ }
213
+ if (result.status !== 0) {
214
+ context.err(`Update failed: npm exited with ${result.status ?? "unknown status"}`);
215
+ return result.status ?? 1;
216
+ }
217
+ context.out("Twinkler updated. Restart your AI session so newly installed skill text is loaded.");
218
+ return 0;
219
+ }
191
220
  async function readAllStdin(stdin) {
192
221
  const chunks = [];
193
222
  for await (const chunk of stdin) {
@@ -1,5 +1,7 @@
1
1
  export interface InstallSkillsOptions {
2
- homeDir: string;
2
+ homeDir?: string;
3
+ detectedAgents?: string[];
4
+ repairTargets?: boolean;
3
5
  }
4
6
  export interface SkillInstallResult {
5
7
  agent: string;
@@ -7,5 +9,6 @@ export interface SkillInstallResult {
7
9
  status: "installed" | "skipped" | "error";
8
10
  targetDir: string;
9
11
  reason?: string;
12
+ contentHash?: string;
10
13
  }
11
- export declare function installBundledSkills(options: InstallSkillsOptions): SkillInstallResult[];
14
+ export declare function installBundledSkills(options?: InstallSkillsOptions): SkillInstallResult[];
@@ -1,58 +1,86 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
- import { basename, dirname, join } from "node:path";
1
+ import { applyAgentOverrides, getTargetDir, installSkills, listDetectedAgents, loadBuiltInRegistry, RECEIPT_FILENAME } from "@mkterswingman/5mghost-agent-skills";
2
+ import { existsSync, readFileSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
3
4
  import { resolveBundledAssetPath } from "./paths.js";
4
- const TARGETS = [
5
- { agent: "codex", skillsDir: ".codex/skills" },
6
- { agent: "agents", skillsDir: ".agents/skills" }
5
+ const AGENT_OVERRIDES = {
6
+ agents: {
7
+ label: "Agents legacy skills",
8
+ detect: { kind: "path", path: "~/.agents" },
9
+ skillsDir: "~/.agents/skills",
10
+ installMethod: "copy"
11
+ }
12
+ };
13
+ const TWINKLER_SKILLS = [
14
+ "setup-5mghost-twinkler",
15
+ "use-5mghost-twinkler",
16
+ "update-5mghost-twinkler"
7
17
  ];
8
- export function installBundledSkills(options) {
18
+ const LEGACY_RECEIPT_FILENAME = ".install-receipt.json";
19
+ export function installBundledSkills(options = {}) {
9
20
  const manifestPath = resolveBundledAssetPath("skills.manifest.json");
10
- const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
11
- const packageRoot = dirname(manifestPath);
12
- const results = [];
13
- for (const skill of manifest.skills) {
14
- const sourceDir = join(packageRoot, skill.source);
15
- for (const target of TARGETS) {
16
- const rootDir = join(options.homeDir, target.skillsDir);
17
- const targetDir = join(rootDir, skill.name);
21
+ const detectedAgents = options.detectedAgents ??
22
+ listDetectedAgents({
23
+ homeDir: options.homeDir,
24
+ agentOverrides: AGENT_OVERRIDES
25
+ });
26
+ const setupTargets = options.repairTargets === false
27
+ ? detectedAgents
28
+ : Array.from(new Set([...detectedAgents, "codex", "agents"]));
29
+ removeLegacyOwnedTargets(options.homeDir, setupTargets);
30
+ return normalizeSummary(installSkills({
31
+ manifestPath,
32
+ homeDir: options.homeDir,
33
+ agentOverrides: AGENT_OVERRIDES,
34
+ detectedAgents: setupTargets
35
+ }));
36
+ }
37
+ function removeLegacyOwnedTargets(homeDir, agentNames) {
38
+ const registry = applyAgentOverrides(loadBuiltInRegistry(), AGENT_OVERRIDES);
39
+ for (const agentName of agentNames) {
40
+ const agent = registry.agents[agentName];
41
+ if (!agent)
42
+ continue;
43
+ for (const skillName of TWINKLER_SKILLS) {
44
+ const targetDir = getTargetDir(homeDir, agent.skillsDir, skillName);
45
+ const receiptPath = findReceiptPath(targetDir);
46
+ if (!existsSync(receiptPath))
47
+ continue;
48
+ let receipt;
18
49
  try {
19
- if (!existsSync(sourceDir)) {
20
- results.push({
21
- agent: target.agent,
22
- skill: skill.name,
23
- status: "error",
24
- targetDir,
25
- reason: `missing bundled skill source ${sourceDir}`
26
- });
27
- continue;
28
- }
29
- mkdirSync(rootDir, { recursive: true });
30
- rmSync(targetDir, { recursive: true, force: true });
31
- cpSync(sourceDir, targetDir, { recursive: true });
32
- writeFileSync(join(targetDir, ".install-receipt.json"), JSON.stringify({
33
- product: manifest.product,
34
- skill: skill.name,
35
- agent: target.agent,
36
- source: basename(sourceDir),
37
- installed_at: new Date().toISOString()
38
- }, null, 2));
39
- results.push({
40
- agent: target.agent,
41
- skill: skill.name,
42
- status: "installed",
43
- targetDir
44
- });
50
+ receipt = JSON.parse(readFileSync(receiptPath, "utf8"));
45
51
  }
46
- catch (error) {
47
- results.push({
48
- agent: target.agent,
49
- skill: skill.name,
50
- status: "error",
51
- targetDir,
52
- reason: error instanceof Error ? error.message : String(error)
53
- });
52
+ catch {
53
+ continue;
54
+ }
55
+ if (receipt.installedBy === undefined &&
56
+ receipt.product === "5mghost-twinkler" &&
57
+ receipt.skill === skillName &&
58
+ receipt.agent === agentName) {
59
+ rmSync(targetDir, { recursive: true, force: true });
54
60
  }
55
61
  }
56
62
  }
57
- return results;
63
+ }
64
+ function findReceiptPath(targetDir) {
65
+ const current = join(targetDir, RECEIPT_FILENAME);
66
+ if (existsSync(current))
67
+ return current;
68
+ return join(targetDir, LEGACY_RECEIPT_FILENAME);
69
+ }
70
+ function normalizeSummary(summary) {
71
+ return summary.results.map((result) => {
72
+ const status = result.status === "installed" || result.status === "updated"
73
+ ? "installed"
74
+ : result.status === "error"
75
+ ? "error"
76
+ : "skipped";
77
+ return {
78
+ agent: result.agent,
79
+ skill: result.skill,
80
+ status,
81
+ targetDir: result.targetDir,
82
+ reason: result.reason,
83
+ contentHash: result.contentHash
84
+ };
85
+ });
58
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mkterswingman/5mghost-twinkler",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Lightweight AI helper for the 5mghost Twinkler API",
5
5
  "type": "module",
6
6
  "engines": {
@@ -9,6 +9,11 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/5mghost/mcp_projects.git",
15
+ "directory": "5mghost-twinkler/client"
16
+ },
12
17
  "bin": {
13
18
  "twinkler": "dist/cli.js"
14
19
  },
@@ -23,13 +28,15 @@
23
28
  "skills",
24
29
  "skills.manifest.json",
25
30
  "install",
31
+ "scripts",
26
32
  "README.md"
27
33
  ],
28
34
  "scripts": {
29
35
  "build": "tsc -p tsconfig.json",
30
36
  "test": "npm run build && node --test tests/*.test.mjs",
31
37
  "prepack": "npm run build",
32
- "pack:dry": "npm pack --dry-run"
38
+ "pack:dry": "npm pack --dry-run",
39
+ "postinstall": "node scripts/postinstall.mjs"
33
40
  },
34
41
  "keywords": [
35
42
  "twinkler",
@@ -37,6 +44,9 @@
37
44
  "ai",
38
45
  "mkterswingman"
39
46
  ],
47
+ "dependencies": {
48
+ "@mkterswingman/5mghost-agent-skills": "^0.0.1"
49
+ },
40
50
  "devDependencies": {
41
51
  "@types/node": "^25.3.2",
42
52
  "typescript": "^5.9.3"
@@ -0,0 +1,51 @@
1
+ // Runs after `npm install -g` to install Twinkler skills into detected AI clients.
2
+ // This script is intentionally non-fatal: npm install must still succeed when
3
+ // an AI client is not installed or a skill target is locked down.
4
+
5
+ const agentOverrides = {
6
+ agents: {
7
+ label: "Agents legacy skills",
8
+ detect: { kind: "path", path: "~/.agents" },
9
+ skillsDir: "~/.agents/skills",
10
+ installMethod: "copy"
11
+ }
12
+ };
13
+
14
+ let listDetectedAgents;
15
+ let installBundledSkills;
16
+
17
+ try {
18
+ ({ listDetectedAgents } = await import("@mkterswingman/5mghost-agent-skills"));
19
+ ({ installBundledSkills } = await import("../dist/skillInstall.js"));
20
+ } catch (error) {
21
+ console.log("[twinkler] agent-skills not available; skipping skill install.");
22
+ process.exit(0);
23
+ }
24
+
25
+ let agents;
26
+ try {
27
+ agents = listDetectedAgents({ agentOverrides });
28
+ } catch (error) {
29
+ console.log("[twinkler] Could not detect AI clients; skipping skill install.");
30
+ process.exit(0);
31
+ }
32
+
33
+ if (!agents || agents.length === 0) {
34
+ console.log("[twinkler] No AI clients detected; skipping skill install.");
35
+ process.exit(0);
36
+ }
37
+
38
+ try {
39
+ const results = installBundledSkills({ detectedAgents: agents, repairTargets: false });
40
+ const installed = results
41
+ .filter((entry) => entry.status === "installed")
42
+ .map((entry) => `${entry.agent}:${entry.skill}`);
43
+ if (installed.length > 0) {
44
+ console.log(`[twinkler] Installed skills: ${installed.join(", ")}`);
45
+ console.log("[twinkler] Restart your AI session before invoking the new skill text.");
46
+ } else {
47
+ console.log("[twinkler] No skill targets updated.");
48
+ }
49
+ } catch (error) {
50
+ console.log(`[twinkler] Skill install failed (non-fatal): ${String(error)}`);
51
+ }
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: setup-5mghost-twinkler
3
+ preamble-tier: 3
4
+ version: 0.2.0
5
+ description: |
6
+ Set up or repair the local Twinkler helper, bundled skills, and mkterswingman
7
+ auth for AI-driven Twitch data workflows.
8
+ ---
9
+
10
+ # Setup 5mghost Twinkler
11
+
12
+ Use when Twinkler is missing, auth is missing or expired, skills may be stale,
13
+ or the user asks to prepare Twinkler for an AI workflow.
14
+
15
+ ## Default Path
16
+
17
+ 1. Check the helper:
18
+
19
+ ```bash
20
+ twinkler doctor
21
+ ```
22
+
23
+ 2. If the helper is missing, install it:
24
+
25
+ ```bash
26
+ npm install -g @mkterswingman/5mghost-twinkler
27
+ ```
28
+
29
+ The npm install runs the bundled skill installer automatically. Use `twinkler
30
+ setup` only as a repair command when postinstall could not update the local AI
31
+ skill directories.
32
+
33
+ 3. Repair skills when needed:
34
+
35
+ ```bash
36
+ twinkler setup
37
+ ```
38
+
39
+ 4. If auth is missing, send the user to:
40
+
41
+ ```text
42
+ https://mkterswingman.com/pat/login
43
+ ```
44
+
45
+ Ask the user to copy the mkterswingman PAT, then start:
46
+
47
+ ```bash
48
+ twinkler auth login --pat-stdin
49
+ ```
50
+
51
+ Have the user paste the PAT into that local input. Do not ask them to paste the
52
+ PAT into chat, screenshots, docs, shell history, or source files.
53
+
54
+ ## Success
55
+
56
+ `twinkler doctor` should report auth ready. Then the AI can use the
57
+ `use-5mghost-twinkler` skill and `twinkler call`.
58
+
59
+ ## Recovery
60
+
61
+ - Missing command: run `npm install -g @mkterswingman/5mghost-twinkler`.
62
+ - Missing skills after install: run `twinkler setup`, then restart the AI session.
63
+ - Missing auth: use the PAT page and `twinkler auth login --pat-stdin`.
64
+ - HTTP 401/403 from API calls: run `twinkler auth status`; if stale, log in again.
65
+ - Permission or npm global install failure: use the same package manager and
66
+ Node installation method that originally installed global npm packages.
67
+
68
+ ## Secret Policy
69
+
70
+ Never print, echo, log, commit, or store PATs in project files. The only normal
71
+ local storage is `~/.mkterswingman/auth.json`, written by the helper.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: update-5mghost-twinkler
3
+ preamble-tier: 3
4
+ version: 0.2.0
5
+ description: |
6
+ Update the Twinkler helper package and refresh bundled AI skills.
7
+ ---
8
+
9
+ # Update 5mghost Twinkler
10
+
11
+ Use when the user asks to update, upgrade, repair stale skills, or pick up a new
12
+ Twinkler helper release.
13
+
14
+ ## Default Path
15
+
16
+ Run:
17
+
18
+ ```bash
19
+ twinkler update
20
+ ```
21
+
22
+ This runs the npm global update for `@mkterswingman/5mghost-twinkler@latest`.
23
+ The package postinstall refreshes bundled skills automatically.
24
+
25
+ If `twinkler update` is unavailable because the installed version is old, run:
26
+
27
+ ```bash
28
+ npm install -g @mkterswingman/5mghost-twinkler@latest
29
+ twinkler setup
30
+ ```
31
+
32
+ Then ask the user to restart the AI session so updated skill text is loaded.
33
+
34
+ ## Verify
35
+
36
+ ```bash
37
+ twinkler version
38
+ twinkler doctor
39
+ ```
40
+
41
+ Do not ask the user to edit skill directories or hidden config files manually.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: use-5mghost-twinkler
3
3
  preamble-tier: 3
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  description: |
6
6
  Use when the user wants Twitch streamer, game, ranking, stream-session, CCV,
7
7
  or chat-watch data from the mkterswingman Twinkler API.
@@ -10,7 +10,7 @@ description: |
10
10
 
11
11
  # Use 5mghost Twinkler
12
12
 
13
- Use the local `twinkler` helper. It adds auth, refreshes short-lived
13
+ Use the local `twinkler` helper. It handles auth, refreshes short-lived
14
14
  mkterswingman access tokens when available, and calls the hosted Twinkler REST
15
15
  API. Prefer the helper over hand-written `curl`.
16
16
 
@@ -22,62 +22,67 @@ Run:
22
22
  twinkler doctor
23
23
  ```
24
24
 
25
- If the helper is missing, ask the user to install it:
26
-
27
- ```bash
28
- npm install -g @mkterswingman/5mghost-twinkler
29
- twinkler setup
30
- ```
25
+ If the helper is missing or auth is missing, use `setup-5mghost-twinkler`.
26
+ Do not ask non-technical users to understand CLI concepts; operate the helper
27
+ for them and explain only the action they need to take.
31
28
 
32
29
  ## Auth
33
30
 
34
- Twinkler uses the shared mkterswingman PAT. Do not call it a Twinkler PAT.
31
+ Twinkler uses the shared mkterswingman login/PAT. Do not call it a Twinkler PAT.
35
32
 
36
33
  If auth is missing:
37
34
 
38
35
  1. Give the user this URL: <https://mkterswingman.com/pat/login>
39
36
  2. Ask the user to copy the mkterswingman PAT.
40
- 3. Start the helper login command and have the user paste the PAT into the
41
- helper input, not into the AI chat.
37
+ 3. Start `twinkler auth login --pat-stdin`.
38
+ 4. Have the user paste the PAT into that local input, not into AI chat.
39
+
40
+ Never echo the PAT back to the user. Never put the PAT in source files, docs,
41
+ logs, URLs, screenshots, PR text, or committed `.env` files. Do not commit
42
+ `~/.mkterswingman/auth.json` or any generated secret/config file.
43
+
44
+ ## Common Calls
42
45
 
43
- Preferred save path when stdin is available:
46
+ Use:
44
47
 
45
48
  ```bash
46
- twinkler auth login --pat-stdin
49
+ twinkler call GET /api/v1/channel/ibai/summary --query days=30
50
+ twinkler call GET /api/v1/rankings/channels --query sort_by=mostfollowers --query days=30 --query page_size=5
47
51
  ```
48
52
 
49
- Then pass the PAT through stdin. Do not put the PAT in command-line arguments
50
- unless no stdin-capable tool is available.
53
+ `twinkler call` only allows Twinkler `/api/v1/*` paths. It returns JSON on
54
+ stdout.
55
+
56
+ ## Watch Jobs
57
+
58
+ Use watch jobs when the user asks to monitor a currently live Twitch channel,
59
+ collect CCV points, collect chat messages, or stop when the stream ends or
60
+ changes game.
51
61
 
52
- Fallback:
62
+ Create:
53
63
 
54
64
  ```bash
55
- twinkler auth login --pat <TOKEN>
65
+ twinkler call POST /api/v1/twitch/watch --json '{"logins":["theburntpeanut"],"collect":["ccv","chat"],"start":{"type":"time","at":"now"},"stop":{"type":"game","game_name":"ARC Raiders"}}'
56
66
  ```
57
67
 
58
- Never echo the PAT back to the user. Never put the PAT in source files, docs,
59
- logs, URLs, screenshots, PR text, or committed `.env` files. Do not commit
60
- `~/.mkterswingman/auth.json` or any generated secret/config file to a remote
61
- repository. Do not ask non-technical users to paste PATs into chat; use the
62
- helper's stdin path or a secure secret-entry UI.
63
-
64
- For AI-managed cloud/server environments, store the PAT as a secret named
65
- `MKTERSWINGMAN_PAT`. Do not explain environment variables to non-technical
66
- users unless they ask; configure the target environment yourself when you have
67
- tool access.
68
+ The response contains a `job_id`. Save it in your working notes. Then poll:
68
69
 
69
- ## Preferred Calls
70
+ ```bash
71
+ twinkler call GET /api/v1/twitch/watch/<job_id>
72
+ twinkler call GET /api/v1/twitch/watch/<job_id>/ccv
73
+ twinkler call GET /api/v1/twitch/watch/<job_id>/chat
74
+ ```
70
75
 
71
- Use:
76
+ Stop/cleanup when the user is done:
72
77
 
73
78
  ```bash
74
- twinkler call GET /api/v1/channel/ibai/summary --query days=30
75
- twinkler call GET /api/v1/rankings/channels --query sort_by=mostfollowers --query days=30 --query page_size=5
76
- twinkler call POST /api/v1/twitch/watch --json '{"logins":["theburntpeanut"],"collect":["ccv","chat"],"start":{"type":"time","at":"now"},"stop":{"type":"game","game_name":"ARC Raiders"}}'
79
+ twinkler call DELETE /api/v1/twitch/watch/<job_id>
77
80
  ```
78
81
 
79
- `twinkler call` only allows Twinkler `/api/v1/*` paths. It returns JSON on
80
- stdout.
82
+ Normal logged-in users can create and delete their own watch jobs. If the API
83
+ returns `WATCH_DISABLED`, explain that the hosted watch worker is not enabled
84
+ right now. If it returns `AUTH_*`, repair mkterswingman auth. If it returns a
85
+ limit error, tell the user which limit was hit and stop creating more jobs.
81
86
 
82
87
  ## Calling The Helper From Scripts
83
88
 
@@ -110,8 +115,6 @@ store the mkterswingman PAT once, then scripts can call `twinkler call`.
110
115
  Use direct REST only when the helper cannot be installed or the runtime is
111
116
  non-Node, such as a Python-only cloud job. Direct REST still requires auth.
112
117
 
113
- Python example:
114
-
115
118
  ```python
116
119
  import os
117
120
  import requests
@@ -1,10 +1,54 @@
1
1
  {
2
+ "schemaVersion": 1,
2
3
  "product": "5mghost-twinkler",
3
4
  "skills": [
5
+ {
6
+ "name": "setup-5mghost-twinkler",
7
+ "source": { "type": "local", "path": "./skills/setup-5mghost-twinkler" },
8
+ "targets": [
9
+ "claude",
10
+ "claude-internal",
11
+ "codex",
12
+ "codex-internal",
13
+ "gemini",
14
+ "gemini-internal",
15
+ "openclaw",
16
+ "workbuddy",
17
+ "codebuddy",
18
+ "agents"
19
+ ]
20
+ },
4
21
  {
5
22
  "name": "use-5mghost-twinkler",
6
- "source": "skills/use-5mghost-twinkler",
7
- "description": "Use the Twinkler helper to call the mkterswingman Twinkler API from AI workflows."
23
+ "source": { "type": "local", "path": "./skills/use-5mghost-twinkler" },
24
+ "targets": [
25
+ "claude",
26
+ "claude-internal",
27
+ "codex",
28
+ "codex-internal",
29
+ "gemini",
30
+ "gemini-internal",
31
+ "openclaw",
32
+ "workbuddy",
33
+ "codebuddy",
34
+ "agents"
35
+ ]
36
+ },
37
+ {
38
+ "name": "update-5mghost-twinkler",
39
+ "source": { "type": "local", "path": "./skills/update-5mghost-twinkler" },
40
+ "targets": [
41
+ "claude",
42
+ "claude-internal",
43
+ "codex",
44
+ "codex-internal",
45
+ "gemini",
46
+ "gemini-internal",
47
+ "openclaw",
48
+ "workbuddy",
49
+ "codebuddy",
50
+ "agents"
51
+ ]
8
52
  }
9
53
  ]
10
54
  }