@assethub/cli 0.1.33 → 0.1.36

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,7 +4,7 @@ Command line interface for AssetHub.
4
4
 
5
5
  ## Quick setup
6
6
 
7
- Connect Claude Code and Codex to AssetHub in one step:
7
+ Connect Claude Code, Codex and Cursor to AssetHub in one step:
8
8
 
9
9
  ```sh
10
10
  assethub setup # prompts for your API key and workspace
@@ -12,25 +12,79 @@ printf '%s' "$KEY" | assethub setup --api-key-stdin --workspace <id> --yes
12
12
  assethub setup --dry-run # show every command and file change, change nothing
13
13
  ```
14
14
 
15
- Setup logs in (reusing a saved key), selects a workspace, registers the
16
- `assethub` MCP server in Claude Code (`claude mcp add-json ... --scope user`, run
17
- again safely), merges an `[mcp_servers.assethub]` section into
18
- `$CODEX_HOME/config.toml` (default `~/.codex`, original saved as
19
- `config.toml.bak-<timestamp>`), then runs `doctor --mcp` and prints a checklist
20
- (`--json` for machine output). Use `--client claude|codex|both` to limit the
21
- targets.
15
+ `setup` runs these steps in order, and is safe to run again: each step checks the
16
+ current state first and reports it as unchanged, updated, or (with `--dry-run`)
17
+ what it would change.
18
+
19
+ | Step | What it does |
20
+ | --- | --- |
21
+ | `login` | Reuses a saved key, or reads one from `--api-key-stdin`, `ASSETHUB_API_KEY` or a prompt. |
22
+ | `workspace` | Uses `--workspace`, the saved selection, or asks you to pick one. |
23
+ | agents | Finds Claude Code, Codex and Cursor on this machine (their folder or command), or uses `--agent`. |
24
+ | `mcp` | Registers the `assethub` MCP server in each agent: Claude Code through `claude mcp add-json ... --scope user` (Claude Code owns `~/.claude.json`; when `claude` is not on PATH, for example with only an IDE extension, setup writes the same entry into that file itself), Codex as an `[mcp_servers.assethub]` section in `$CODEX_HOME/config.toml`, Cursor as `mcpServers.assethub` in `~/.cursor/mcp.json`. A file setup writes itself is backed up as `<file>.bak-<timestamp>` first and replaced in one step. If `claude mcp add` fails while replacing an outdated entry, the previous entry is put back. |
25
+ | `skills` | Installs the bundled `assethub` skill under `~/.agents/skills/assethub` and links it into each agent's skill folder, so the agent knows the command sequence, cost rules and recovery steps. |
26
+ | `app-env` | macOS: makes `ASSETHUB_API_KEY` visible to apps started from the Dock (see below). |
27
+ | `doctor` | Runs `doctor --mcp` and prints a checklist (`--json` for machine output). |
28
+
29
+ Choose agents with `--agent claude-code|codex|cursor` (repeat it, or separate
30
+ names with commas; `claude` also works). Run only some steps with
31
+ `--only <step,...>` or leave some out with `--skip <step,...>`; `--no-skills` and
32
+ `--no-app-env` are short for skipping those. `--project` writes the MCP config
33
+ and the skill into the current folder instead of your home folder
34
+ (`.mcp.json`, `.codex/config.toml`, `.cursor/mcp.json`, `.agents/skills/assethub`
35
+ with relative links), for a repository to share:
22
36
 
23
- MCP does not need the CLI. Setup is only a shortcut: both clients call the hosted
37
+ ```sh
38
+ assethub setup --only skills --project # put the agent guidance in this repository
39
+ assethub setup --only mcp,skills # wire the agents without logging in
40
+ assethub doctor --setup # report missing or outdated MCP config and skill
41
+ ```
42
+
43
+ `assethub doctor --setup` checks the same steps without changing anything and
44
+ fails when an agent's MCP entry or the skill is missing or out of date; run
45
+ `assethub setup` to fix it. Directories setup did not create are left untouched.
46
+ After a CLI upgrade, the next command refreshes the installed skill automatically.
47
+ Set `ASSETHUB_CLI_HOME` to redirect these writes, for example in tests.
48
+
49
+ `assethub init` is deprecated: it now runs `setup --only mcp,skills` and prints
50
+ the result as JSON, and will be removed in a later version. `--client
51
+ claude|codex|both` still works and means the same as `--agent`.
52
+
53
+ MCP does not need the CLI. Setup is only a shortcut: every agent calls the hosted
24
54
  `/api/mcp` server directly and read the key from the `ASSETHUB_API_KEY`
25
55
  environment variable. The Claude Code entry is
26
56
  `claude mcp add-json assethub '{"type":"http","url":"https://app.assethub.io/api/mcp","headers":{"Authorization":"Bearer ${ASSETHUB_API_KEY}","X-AssetHub-Workspace":"<id>"}}' --scope user`,
27
57
  so the key is never written to Claude Code's config or passed on a command line,
28
- and removing the CLI does not disconnect MCP. Codex uses `bearer_token_env_var`.
58
+ and removing the CLI does not disconnect MCP. Codex uses `bearer_token_env_var`,
59
+ and Cursor `${env:ASSETHUB_API_KEY}`.
29
60
  Setup never writes the key to a shell profile; run `assethub setup --print-env` to
30
61
  print the `export ASSETHUB_API_KEY=...` line, and export it in the shell that
31
62
  starts Claude Code or Codex. The key is redacted
32
63
  (`ah_…last4`) everywhere else.
33
64
 
65
+ ## Staying up to date
66
+
67
+ ```sh
68
+ assethub update --check # JSON: up_to_date or update_available
69
+ assethub update # upgrade a global install now; the saved login is kept
70
+ assethub setup --no-auto-update # turn background updates off (--auto-update turns them on)
71
+ ```
72
+
73
+ Auto-update is on by default. Once a day, a detached background process checks
74
+ for a newer release and installs it with the package manager that installed the
75
+ CLI, the same way `assethub update --yes` does, keeping the saved login. No
76
+ command waits for it, and an install never happens inside a running command; the
77
+ next command prints one line on stderr (`updated itself from 0.1.36 to 0.1.37`).
78
+ stdout stays one JSON object. If the install fails, the CLI keeps the current
79
+ version and says so, then keeps announcing the newer release.
80
+
81
+ It applies only to a global install (not `npx`, a project dependency or a source
82
+ checkout) and is off in CI, with `ASSETHUB_NO_AUTO_UPDATE=1` (no install, notice
83
+ only) or with `ASSETHUB_NO_UPDATE_CHECK=1` (no check at all). The choice is saved
84
+ in `~/.assethub/settings.json`, and `assethub setup` shows it as its `auto-update`
85
+ step. The bundled agent skill also asks agents to run `assethub update --check`
86
+ at the start of each session.
87
+
34
88
  ## API discovery and Workspace Skills
35
89
 
36
90
  Use `api search [query]` to discover the authenticated server catalog and
@@ -146,8 +200,7 @@ authentication. Existing workspace keys and user-token login remain supported.
146
200
  ```sh
147
201
  assethub --version
148
202
  assethub doctor --mcp --profile my-workspace
149
- assethub init --dry-run
150
- assethub init
203
+ assethub doctor --setup
151
204
  assethub mcp config --client cursor
152
205
  assethub mcp config --client codex
153
206
  ```
@@ -168,18 +221,8 @@ Use the same key as your CLI profile. Personal profiles include the selected
168
221
  For a different API origin, pass
169
222
  `--base-url https://your-host/prefix` (HTTP is accepted only for localhost).
170
223
 
171
- `init` sets up every supported coding agent found on this machine — Claude Code,
172
- Codex, and Cursor — in one step. It writes the hosted MCP entry into each agent's
173
- own configuration (merging with what is already there), installs the bundled
174
- `assethub` skill under `~/.agents/skills/assethub`, and links it into each agent's
175
- skill directory so the agent knows the correct command sequence, cost rules, and
176
- recovery steps without being told. Pass `--agent <name>` to choose agents
177
- explicitly, `--project` to write project-level MCP files into the current
178
- directory instead of your home, and `--dry-run` to see the plan without writing.
179
- `init` never stores a key: the MCP entries read `ASSETHUB_API_KEY` from the
180
- agent's environment. Directories that `init` did not create are left untouched.
181
- After a CLI upgrade, the next command refreshes the installed skill automatically.
182
- Set `ASSETHUB_CLI_HOME` to redirect all of these writes, for example in tests.
224
+ To write these settings into each agent for you, run `assethub setup` (see
225
+ Quick setup above).
183
226
 
184
227
  Explicit `--profile` uses that profile's key and origin, ahead of environment
185
228
  variables, including user tokens and workspace MFA proofs. A missing profile fails.
@@ -1,24 +1,36 @@
1
1
  export declare const AGENT_IDS: readonly ["claude-code", "codex", "cursor"];
2
2
  export type AgentId = (typeof AGENT_IDS)[number];
3
- type AgentSpec = {
3
+ export type AgentSpec = {
4
4
  id: AgentId;
5
5
  name: string;
6
- /** Presence of this directory under home means the agent is installed. */
6
+ /** The command on PATH; finding it, or `detectDir` under home, means the agent is installed. */
7
+ binary: string;
7
8
  detectDir: string;
8
- globalMcpPath: string;
9
- projectMcpPath: string;
10
9
  skillDir: string;
11
10
  format: 'claude' | 'cursor' | 'codex';
12
11
  };
12
+ export declare const AGENT_SPECS: readonly AgentSpec[];
13
+ export declare const agentSpec: (id: AgentId) => AgentSpec;
14
+ /** `--agent` values: repeatable or comma separated; `claude` is accepted for `claude-code`. Keeps AGENT_IDS order. */
15
+ export declare const parseAgentIds: (values: string[]) => AgentId[];
16
+ /** Agents installed here: their folder exists under home (or $CODEX_HOME for Codex), or their command is on PATH. */
17
+ export declare const detectAgents: (home: string, findBinary: (name: string) => Promise<string | undefined>, codexHome?: string) => Promise<AgentId[]>;
13
18
  /** `ASSETHUB_CLI_HOME` keeps tests and sandboxes from writing into the real home directory. */
14
19
  export declare const cliHome: () => string;
15
20
  export declare const packagedSkillDir: () => string;
16
21
  export declare const canonicalSkillDir: (home: string) => string;
17
22
  export declare const mcpServerEntry: (format: AgentSpec["format"], baseUrl: string, workspaceId?: string) => Record<string, unknown>;
18
- type WriteStatus = 'created' | 'updated' | 'unchanged';
19
- type SkillStatus = 'installed' | 'updated' | 'unchanged' | 'kept-existing';
20
- export declare const installSkill: ({ home, version, dryRun, }: {
21
- home: string;
23
+ /** The `mcpServers.assethub` entry of a JSON config, or undefined when the file has none or cannot be read. */
24
+ export declare const readJsonServer: (text: string | undefined) => unknown;
25
+ /**
26
+ * Sets `mcpServers.assethub` in a JSON config and keeps every other key.
27
+ * Returns the new text, or the input unchanged when the entry already matches.
28
+ */
29
+ export declare const mergeJsonServer: (path: string, text: string | undefined, entry: Record<string, unknown>) => string;
30
+ export type SkillStatus = 'installed' | 'updated' | 'unchanged' | 'kept-existing' | 'kept-newer';
31
+ export declare const installSkill: ({ root, version, dryRun, }: {
32
+ /** The home folder, or the project folder for a project install. */
33
+ root: string;
22
34
  version: string;
23
35
  dryRun: boolean;
24
36
  }) => Promise<{
@@ -26,44 +38,27 @@ export declare const installSkill: ({ home, version, dryRun, }: {
26
38
  status: SkillStatus;
27
39
  version?: string;
28
40
  }>;
29
- type LinkStatus = 'linked' | 'relinked' | 'unchanged' | 'kept-existing';
41
+ export type LinkStatus = 'linked' | 'relinked' | 'unchanged' | 'kept-existing';
30
42
  /**
31
43
  * Refresh an installed skill after a CLI upgrade. Runs before every command;
32
44
  * the steady-state cost is one small file read. Never throws — a read-only or
33
45
  * missing home must not fail the user's actual command.
34
46
  */
35
47
  export declare const syncInstalledSkill: () => Promise<void>;
36
- export type InitOptions = {
37
- baseUrl: string;
38
- workspaceId?: string;
39
- /** Agent ids to configure; defaults to every agent detected under home. */
40
- agents?: string[];
41
- /** Write project-level MCP config into the working directory instead of the user's home. */
42
- project?: boolean;
43
- dryRun?: boolean;
44
- cwd?: string;
45
- home?: string;
46
- };
47
- export type InitReport = {
48
- version: string;
49
- dryRun: boolean;
50
- scope: 'global' | 'project';
51
- skill: Awaited<ReturnType<typeof installSkill>>;
52
- agents: {
53
- id: AgentId;
54
- name: string;
55
- detected: boolean;
56
- mcp: {
57
- path: string;
58
- status: WriteStatus;
59
- };
60
- skill: {
61
- path: string;
62
- status: LinkStatus;
63
- };
48
+ export type SkillsReport = {
49
+ path: string;
50
+ status: SkillStatus;
51
+ version?: string;
52
+ links: {
53
+ agent: AgentId;
54
+ path: string;
55
+ status: LinkStatus;
64
56
  }[];
65
- next: string[];
66
57
  };
67
- export declare const initAgents: (options: InitOptions) => Promise<InitReport>;
68
- export {};
58
+ /** Installs the skill under `<root>/.agents/skills` and links it into each agent's skill folder under root. */
59
+ export declare const installAgentSkills: ({ root, agents, dryRun, }: {
60
+ root: string;
61
+ agents: AgentId[];
62
+ dryRun: boolean;
63
+ }) => Promise<SkillsReport>;
69
64
  //# sourceMappingURL=agentSetup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agentSetup.d.ts","sourceRoot":"","sources":["../src/agentSetup.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,SAAS,6CAA8C,CAAA;AACpE,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhD,KAAK,SAAS,GAAG;IACf,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;CACtC,CAAA;AAoCD,+FAA+F;AAC/F,eAAO,MAAM,OAAO,QAAO,MAA4C,CAAA;AAEvE,eAAO,MAAM,gBAAgB,QAAO,MACiC,CAAA;AAErE,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,MACJ,CAAA;AA2B7C,eAAO,MAAM,cAAc,GACzB,QAAQ,SAAS,CAAC,QAAQ,CAAC,EAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAA;AAED,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAA;AA2EtD,KAAK,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,eAAe,CAAA;AAmC1E,eAAO,MAAM,YAAY,GAAU,4BAIhC;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB,KAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,CAwBhE,CAAA;AAED,KAAK,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,eAAe,CAAA;AAkCvE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,IAAI,CAevD,CAAA;AAID,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IAC/C,MAAM,EAAE;QACN,EAAE,EAAE,OAAO,CAAA;QACX,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,OAAO,CAAA;QACjB,GAAG,EAAE;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,WAAW,CAAA;SAAC,CAAA;QACxC,KAAK,EAAE;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,UAAU,CAAA;SAAC,CAAA;KAC1C,EAAE,CAAA;IACH,IAAI,EAAE,MAAM,EAAE,CAAA;CACf,CAAA;AAED,eAAO,MAAM,UAAU,GAAU,SAAS,WAAW,KAAG,OAAO,CAAC,UAAU,CA4EzE,CAAA"}
1
+ {"version":3,"file":"agentSetup.d.ts","sourceRoot":"","sources":["../src/agentSetup.ts"],"names":[],"mappings":"AAkCA,eAAO,MAAM,SAAS,6CAA8C,CAAA;AACpE,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhD,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,gGAAgG;IAChG,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;CACtC,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAyB3C,CAAA;AAED,eAAO,MAAM,SAAS,GAAI,IAAI,OAAO,KAAG,SACiB,CAAA;AAEzD,sHAAsH;AACtH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,EAAE,KAAG,OAAO,EAOvD,CAAA;AAED,qHAAqH;AACrH,eAAO,MAAM,YAAY,GACvB,MAAM,MAAM,EACZ,YAAY,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,EACzD,YAAY,MAAM,KACjB,OAAO,CAAC,OAAO,EAAE,CAUnB,CAAA;AAMD,+FAA+F;AAC/F,eAAO,MAAM,OAAO,QAAO,MAA4C,CAAA;AAEvE,eAAO,MAAM,gBAAgB,QAAO,MACiC,CAAA;AAErE,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,MACJ,CAAA;AA2B7C,eAAO,MAAM,cAAc,GACzB,QAAQ,SAAS,CAAC,QAAQ,CAAC,EAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAA;AAED,+GAA+G;AAC/G,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,GAAG,SAAS,KAAG,OAQzD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EACZ,MAAM,MAAM,GAAG,SAAS,EACxB,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MA2BF,CAAA;AAID,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,eAAe,GAAG,YAAY,CAAA;AAmChG,eAAO,MAAM,YAAY,GAAU,4BAIhC;IACD,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB,KAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,CA2BhE,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,eAAe,CAAA;AAkC9E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,IAAI,CAgBvD,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAC,EAAE,CAAA;CAC5D,CAAA;AAED,+GAA+G;AAC/G,eAAO,MAAM,kBAAkB,GAAU,2BAItC;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,EAAE,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;CAChB,KAAG,OAAO,CAAC,YAAY,CAMvB,CAAA"}
@@ -1,53 +1,69 @@
1
1
  /**
2
- * `assethub init` and the skill sync that keeps its output current.
2
+ * The agent-facing half of `assethub setup`: which coding agents are on this
3
+ * machine, where their MCP config lives, and the skill they read on demand.
3
4
  *
4
5
  * Coding agents learn a tool from two local files: an MCP server entry in their
5
- * own config, and a skill directory they read on demand. `init` writes both for
6
- * every supported agent found on this machine, merging into existing config
7
- * rather than replacing it, and never touching credentials — the MCP entries
6
+ * own config, and a skill directory. `setup` writes both, merging into existing
7
+ * config rather than replacing it and never writing credentials: the MCP entries
8
8
  * reference `ASSETHUB_API_KEY` from the agent's environment.
9
9
  *
10
- * The skill is copied once to `~/.agents/skills/assethub` and symlinked into
11
- * each agent's skill directory. A version marker lets every later command
12
- * refresh the copy when the CLI has been upgraded, so agents never read rules
13
- * written for a version they are no longer running.
10
+ * The skill is copied once to `<root>/.agents/skills/assethub` (root is the home
11
+ * folder, or the project with --project) and symlinked into each agent's skill
12
+ * directory. A version marker lets every later command refresh the home copy
13
+ * when the CLI has been upgraded, so agents never read rules written for a
14
+ * version they are no longer running.
14
15
  */
15
16
  import { cp, lstat, mkdir, readFile, readlink, rename, rm, stat, symlink, writeFile, } from 'node:fs/promises';
16
17
  import { homedir } from 'node:os';
17
18
  import { dirname, join, relative, resolve } from 'node:path';
18
- import { cwd as processCwd, env, pid } from 'node:process';
19
+ import { env, pid } from 'node:process';
19
20
  import { fileURLToPath } from 'node:url';
20
- import { cliVersion, mcpConfig, validatedBaseUrl } from './setup.js';
21
+ import { cliVersion, validatedBaseUrl } from './setup.js';
22
+ import { compareVersions } from './update.js';
21
23
  export const AGENT_IDS = ['claude-code', 'codex', 'cursor'];
22
- const AGENTS = [
24
+ export const AGENT_SPECS = [
23
25
  {
24
26
  id: 'claude-code',
25
27
  name: 'Claude Code',
28
+ binary: 'claude',
26
29
  detectDir: '.claude',
27
- globalMcpPath: '.claude.json',
28
- projectMcpPath: '.mcp.json',
29
30
  skillDir: '.claude/skills',
30
31
  format: 'claude',
31
32
  },
32
33
  {
33
34
  id: 'codex',
34
35
  name: 'Codex',
36
+ binary: 'codex',
35
37
  detectDir: '.codex',
36
- globalMcpPath: '.codex/config.toml',
37
- projectMcpPath: '.codex/config.toml',
38
38
  skillDir: '.codex/skills',
39
39
  format: 'codex',
40
40
  },
41
41
  {
42
42
  id: 'cursor',
43
43
  name: 'Cursor',
44
+ binary: 'cursor',
44
45
  detectDir: '.cursor',
45
- globalMcpPath: '.cursor/mcp.json',
46
- projectMcpPath: '.cursor/mcp.json',
47
46
  skillDir: '.cursor/skills',
48
47
  format: 'cursor',
49
48
  },
50
49
  ];
50
+ export const agentSpec = (id) => AGENT_SPECS.find(agent => agent.id === id);
51
+ /** `--agent` values: repeatable or comma separated; `claude` is accepted for `claude-code`. Keeps AGENT_IDS order. */
52
+ export const parseAgentIds = (values) => {
53
+ const names = values.flatMap(value => value.split(',')).map(value => value.trim().toLowerCase()).filter(Boolean);
54
+ const ids = names.map(name => (name === 'claude' ? 'claude-code' : name));
55
+ const unknown = ids.filter(id => !AGENT_IDS.includes(id));
56
+ if (unknown.length > 0)
57
+ throw new Error(`Unknown agent: ${unknown.join(', ')}. Use --agent ${AGENT_IDS.join('|')}.`);
58
+ return AGENT_IDS.filter(id => ids.includes(id));
59
+ };
60
+ /** Agents installed here: their folder exists under home (or $CODEX_HOME for Codex), or their command is on PATH. */
61
+ export const detectAgents = async (home, findBinary, codexHome) => {
62
+ const found = await Promise.all(AGENT_SPECS.map(async (agent) => (await isDirectory(join(home, agent.detectDir))) ||
63
+ (agent.id === 'codex' && codexHome !== undefined && (await isDirectory(codexHome))) ||
64
+ (await findBinary(agent.binary)) !== undefined));
65
+ return AGENT_SPECS.filter((_, index) => found[index]).map(agent => agent.id);
66
+ };
51
67
  const SERVER_NAME = 'assethub';
52
68
  const SKILL_NAME = 'assethub';
53
69
  const VERSION_MARKER = '.assethub-cli-version';
@@ -92,16 +108,31 @@ export const mcpServerEntry = (format, baseUrl, workspaceId) => {
92
108
  },
93
109
  };
94
110
  };
95
- const writeJsonServer = async (path, entry, dryRun) => {
96
- const text = await readText(path);
111
+ /** The `mcpServers.assethub` entry of a JSON config, or undefined when the file has none or cannot be read. */
112
+ export const readJsonServer = (text) => {
113
+ if (text === undefined)
114
+ return undefined;
115
+ try {
116
+ const parsed = JSON.parse(text);
117
+ return parsed?.mcpServers?.[SERVER_NAME];
118
+ }
119
+ catch {
120
+ return undefined;
121
+ }
122
+ };
123
+ /**
124
+ * Sets `mcpServers.assethub` in a JSON config and keeps every other key.
125
+ * Returns the new text, or the input unchanged when the entry already matches.
126
+ */
127
+ export const mergeJsonServer = (path, text, entry) => {
97
128
  let document = {};
98
- if (text !== undefined) {
129
+ if (text !== undefined && text.trim() !== '') {
99
130
  let parsed;
100
131
  try {
101
132
  parsed = JSON.parse(text);
102
133
  }
103
134
  catch {
104
- throw new Error(`${path} is not valid JSON. Fix or move it, then rerun init.`);
135
+ throw new Error(`${path} is not valid JSON. Fix or move it, then rerun setup.`);
105
136
  }
106
137
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
107
138
  throw new Error(`${path} must contain a JSON object.`);
@@ -112,45 +143,11 @@ const writeJsonServer = async (path, entry, dryRun) => {
112
143
  (typeof document.mcpServers !== 'object' ||
113
144
  document.mcpServers === null ||
114
145
  Array.isArray(document.mcpServers)))
115
- throw new Error(`${path} has a non-object "mcpServers" value. Fix or move it, then rerun init.`);
146
+ throw new Error(`${path} has a non-object "mcpServers" value. Fix or move it, then rerun setup.`);
116
147
  const servers = (document.mcpServers ?? {});
117
- if (JSON.stringify(servers[SERVER_NAME]) === JSON.stringify(entry))
118
- return 'unchanged';
119
- const next = { ...document, mcpServers: { ...servers, [SERVER_NAME]: entry } };
120
- if (!dryRun) {
121
- await mkdir(dirname(path), { recursive: true });
122
- await writeFile(path, `${JSON.stringify(next, null, 2)}\n`);
123
- }
124
- return text === undefined ? 'created' : 'updated';
125
- };
126
- /** Replaces or appends one `[mcp_servers.assethub]` table; every other line is kept verbatim. */
127
- const writeTomlSection = async (path, section, dryRun) => {
128
- const header = `[mcp_servers.${SERVER_NAME}]`;
129
- const body = section.trimEnd();
130
- const text = await readText(path);
131
- const lines = (text ?? '').split('\n');
132
- const start = lines.findIndex(line => line.trim() === header);
133
- let next;
134
- if (start === -1) {
135
- const base = (text ?? '').trimEnd();
136
- next = `${base ? `${base}\n\n` : ''}${body}\n`;
137
- }
138
- else {
139
- const endOffset = lines
140
- .slice(start + 1)
141
- .findIndex(line => /^\s*\[/.test(line));
142
- const end = endOffset === -1 ? lines.length : start + 1 + endOffset;
143
- if (lines.slice(start, end).join('\n').trimEnd() === body)
144
- return 'unchanged';
145
- const before = lines.slice(0, start).join('\n').trimEnd();
146
- const after = lines.slice(end).join('\n').trim();
147
- next = `${before ? `${before}\n\n` : ''}${body}\n${after ? `\n${after}\n` : ''}`;
148
- }
149
- if (!dryRun) {
150
- await mkdir(dirname(path), { recursive: true });
151
- await writeFile(path, next);
152
- }
153
- return text === undefined ? 'created' : 'updated';
148
+ if (text !== undefined && JSON.stringify(servers[SERVER_NAME]) === JSON.stringify(entry))
149
+ return text;
150
+ return `${JSON.stringify({ ...document, mcpServers: { ...servers, [SERVER_NAME]: entry } }, null, 2)}\n`;
154
151
  };
155
152
  const readMarker = async (dir) => (await readText(join(dir, VERSION_MARKER)))?.trim();
156
153
  /**
@@ -182,11 +179,11 @@ const replaceSkillDir = async (source, target, version) => {
182
179
  if (hadTarget)
183
180
  await rm(previous, { recursive: true, force: true });
184
181
  };
185
- export const installSkill = async ({ home, version, dryRun, }) => {
182
+ export const installSkill = async ({ root, version, dryRun, }) => {
186
183
  const source = packagedSkillDir();
187
184
  if (!(await isDirectory(source)))
188
185
  throw new Error('This CLI installation does not include the packaged agent skill.');
189
- const target = canonicalSkillDir(home);
186
+ const target = canonicalSkillDir(root);
190
187
  const marker = await readMarker(target);
191
188
  // A directory without our marker is the user's own; leave it alone.
192
189
  const status = marker === undefined
@@ -195,13 +192,16 @@ export const installSkill = async ({ home, version, dryRun, }) => {
195
192
  : 'installed'
196
193
  : marker === version
197
194
  ? 'unchanged'
198
- : 'updated';
195
+ : // An older CLI (or a stale monorepo build) never replaces a newer skill.
196
+ compareVersions(version, marker) < 0
197
+ ? 'kept-newer'
198
+ : 'updated';
199
199
  if (!dryRun && (status === 'installed' || status === 'updated'))
200
200
  await replaceSkillDir(source, target, version);
201
201
  return {
202
202
  path: target,
203
203
  status,
204
- ...(status === 'kept-existing' ? {} : { version }),
204
+ ...(status === 'kept-existing' ? {} : { version: status === 'kept-newer' ? marker : version }),
205
205
  };
206
206
  };
207
207
  const linkSkill = async (agentSkillsDir, canonical, dryRun) => {
@@ -248,7 +248,8 @@ export const syncInstalledSkill = async () => {
248
248
  if (installed === undefined)
249
249
  return;
250
250
  const version = await cliVersion();
251
- if (installed === version)
251
+ // Only an upgrade refreshes it: an older CLI never overwrites a newer skill.
252
+ if (installed === version || compareVersions(version, installed) < 0)
252
253
  return;
253
254
  const source = packagedSkillDir();
254
255
  if (!(await isDirectory(source)))
@@ -259,53 +260,11 @@ export const syncInstalledSkill = async () => {
259
260
  // best effort
260
261
  }
261
262
  };
262
- export const initAgents = async (options) => {
263
- const home = options.home ?? cliHome();
264
- const cwd = options.cwd ?? processCwd();
265
- const dryRun = options.dryRun ?? false;
266
- const version = await cliVersion();
267
- const requested = options.agents ?? [];
268
- const unknown = requested.filter(id => !AGENT_IDS.includes(id));
269
- if (unknown.length > 0)
270
- throw new Error(`Unknown agent: ${unknown.join(', ')}. Use --agent ${AGENT_IDS.join('|')}.`);
271
- const detected = new Map(await Promise.all(AGENTS.map(async (agent) => [agent.id, await isDirectory(join(home, agent.detectDir))])));
272
- const selected = requested.length > 0
273
- ? AGENTS.filter(agent => requested.includes(agent.id))
274
- : AGENTS.filter(agent => detected.get(agent.id));
275
- if (selected.length === 0)
276
- throw new Error(`No supported coding agent found under ${home}. Pass --agent ${AGENT_IDS.join('|')} to choose one.`);
277
- const skill = await installSkill({ home, version, dryRun });
278
- const agents = [];
279
- for (const spec of selected) {
280
- const mcpPath = options.project
281
- ? join(cwd, spec.projectMcpPath)
282
- : join(home, spec.globalMcpPath);
283
- const mcp = spec.format === 'codex'
284
- ? await writeTomlSection(mcpPath, mcpConfig('codex', options.baseUrl, false, options.workspaceId), dryRun)
285
- : await writeJsonServer(mcpPath, mcpServerEntry(spec.format, options.baseUrl, options.workspaceId), dryRun);
286
- const link = await linkSkill(join(home, spec.skillDir), skill.path, dryRun);
287
- agents.push({
288
- id: spec.id,
289
- name: spec.name,
290
- detected: detected.get(spec.id) ?? false,
291
- mcp: { path: mcpPath, status: mcp },
292
- skill: link,
293
- });
294
- }
295
- return {
296
- version,
297
- dryRun,
298
- scope: options.project ? 'project' : 'global',
299
- skill,
300
- agents,
301
- next: [
302
- ...(env.ASSETHUB_API_KEY
303
- ? []
304
- : [
305
- "Provide ASSETHUB_API_KEY in each agent's environment; the MCP entries read it from there and never store it.",
306
- ]),
307
- 'Restart the agent so it reloads MCP servers and skills.',
308
- 'Run `assethub doctor --mcp` to verify the connection.',
309
- ],
310
- };
263
+ /** Installs the skill under `<root>/.agents/skills` and links it into each agent's skill folder under root. */
264
+ export const installAgentSkills = async ({ root, agents, dryRun, }) => {
265
+ const skill = await installSkill({ root, version: await cliVersion(), dryRun });
266
+ const links = [];
267
+ for (const agent of agents)
268
+ links.push({ agent, ...(await linkSkill(join(root, agentSpec(agent).skillDir), skill.path, dryRun)) });
269
+ return { ...skill, links };
311
270
  };
@@ -9,6 +9,7 @@ export type HooksCommandDeps = {
9
9
  now?: () => Date;
10
10
  startUpload?: (sessionDir: string, profile?: string) => void;
11
11
  startSweep?: (profile?: string) => void;
12
+ startWorkingUpload?: (sessionDir: string, profile?: string) => void;
12
13
  };
13
14
  export declare const runHooksCommand: (argv: string[], deps?: HooksCommandDeps) => Promise<number>;
14
15
  //# sourceMappingURL=command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/hooks/command.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACjC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,CAAA;AA+CD,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EAAE,EACd,OAAM,gBAAqB,KAC1B,OAAO,CAAC,MAAM,CAiIhB,CAAA"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/hooks/command.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACjC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACpE,CAAA;AA+CD,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,EAAE,EACd,OAAM,gBAAqB,KAC1B,OAAO,CAAC,MAAM,CAkIhB,CAAA"}
@@ -2,7 +2,7 @@
2
2
  // runHooksCommand(argv).
3
3
  import { installHooks, sessionSaveDisclosure, uninstallHooks } from './install.js';
4
4
  import { listSessionDirs, readMeta } from './paths.js';
5
- import { saveSession } from './save.js';
5
+ import { saveSession, spawnDetachedSessionUpload } from './save.js';
6
6
  import { canUploadSessions, resolveUploadAuth, uploadPending, uploadSession } from './upload.js';
7
7
  const USAGE = 'Usage: assethub hooks <install|uninstall|save|upload|list|status> [--client claude|codex] [--json]\n' +
8
8
  ' install|uninstall [--global] (the current folder by default; --global for every project)\n' +
@@ -118,6 +118,7 @@ export const runHooksCommand = async (argv, deps = {}) => {
118
118
  upload: { profile: uploadOptions.profile },
119
119
  startUpload: deps.startUpload,
120
120
  startSweep: deps.startSweep,
121
+ startWorkingUpload: deps.startWorkingUpload ?? spawnDetachedSessionUpload,
121
122
  });
122
123
  // A hook must never fail the agent, so save always exits 0.
123
124
  // Claude Code adds SessionStart hook output to the model's context, so
@@ -22,6 +22,12 @@ export type SaveSessionOptions = {
22
22
  startUpload?: (sessionDir: string, profile?: string) => void;
23
23
  /** Starts the background retry of pending sessions on SessionStart. */
24
24
  startSweep?: (profile?: string) => void;
25
+ /**
26
+ * Starts `assethub hooks upload --session <dir> --auto` for a session that is
27
+ * still running (Stop, PreCompact). Omitted: no upload while working. The
28
+ * hooks command passes spawnDetachedSessionUpload; tests pass a stub.
29
+ */
30
+ startWorkingUpload?: (sessionDir: string, profile?: string) => void;
25
31
  };
26
32
  export type SaveSessionResult = {
27
33
  sessionDir: string;
@@ -33,6 +39,8 @@ export type SaveSessionResult = {
33
39
  };
34
40
  /** SessionEnd: upload this session, then retry up to SWEEP_LIMIT older pending ones. */
35
41
  export declare const spawnDetachedUpload: (sessionDir: string, profile?: string) => void;
42
+ /** Stop / PreCompact: upload this running session only; no sweep. */
43
+ export declare const spawnDetachedSessionUpload: (sessionDir: string, profile?: string) => void;
36
44
  /** SessionStart: retry up to SWEEP_LIMIT pending sessions. */
37
45
  export declare const spawnDetachedSweep: (profile?: string) => void;
38
46
  export declare const retentionDays: (env: NodeJS.ProcessEnv) => number | null;
@@ -42,6 +50,12 @@ export declare const pruneSessions: (root: string, now: Date, days: number, keep
42
50
  export declare const ABANDONED_AFTER_MS: number;
43
51
  /** Sessions retried in the background per SessionStart / SessionEnd. */
44
52
  export declare const SWEEP_LIMIT = 3;
53
+ /**
54
+ * A running session is uploaded at most this often. Claude Code app sessions
55
+ * stay open for days and rarely reach SessionEnd, so without this they would
56
+ * only be uploaded once abandoned (ABANDONED_AFTER_MS quiet, then a new session).
57
+ */
58
+ export declare const WORKING_UPLOAD_INTERVAL_MS: number;
45
59
  /**
46
60
  * Copy + redact the transcript Claude Code keeps at `meta.transcriptPath`, and
47
61
  * collect its images. Runs once per SessionEnd and before an upload, never per
@@ -1 +1 @@
1
- {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/hooks/save.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAC,UAAU,EAAE,SAAS,EAAgB,WAAW,EAAC,MAAM,YAAY,CAAA;AAChF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAA;AAE9C,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,eAAO,MAAM,eAAe,QAAmB,CAAA;AAC/C,eAAO,MAAM,sBAAsB,KAAK,CAAA;AASxC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAA;CACjC,CAAA;AAYD,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,IACgC,CAAA;AAE3G,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,IAC+B,CAAA;AAErF,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,CAAC,UAAU,KAAG,MAAM,GAAG,IAM/D,CAAA;AAED,4FAA4F;AAC5F,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,KAAK,IAAI,EACT,MAAM,MAAM,EACZ,MAAM,MAAM,KACX,OAAO,CAAC,MAAM,CAwBhB,CAAA;AAgFD,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,QAAqB,CAAA;AACpD,wEAAwE;AACxE,eAAO,MAAM,WAAW,IAAI,CAAA;AAK5B;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,MAAM,EAClB,UAAS;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAM,KAC/D,OAAO,CAAC,WAAW,GAAG,IAAI,CA6C5B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,MAAM,EACZ,KAAK,IAAI,EACT,mBAAmB,MAAM,KACxB,OAAO,CAAC,MAAM,CAmBhB,CAAA;AAED,eAAO,MAAM,WAAW,GAAU,UAAS,kBAAuB,KAAG,OAAO,CAAC,iBAAiB,CAkF7F,CAAA"}
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/hooks/save.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAC,UAAU,EAAE,SAAS,EAAgB,WAAW,EAAC,MAAM,YAAY,CAAA;AAChF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAA;AAE9C,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,eAAO,MAAM,eAAe,QAAmB,CAAA;AAC/C,eAAO,MAAM,sBAAsB,KAAK,CAAA;AASxC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACpE,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAA;CACjC,CAAA;AAYD,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,IACgC,CAAA;AAE3G,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,IACP,CAAA;AAE3E,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,IAC+B,CAAA;AAErF,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,CAAC,UAAU,KAAG,MAAM,GAAG,IAM/D,CAAA;AAED,4FAA4F;AAC5F,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,KAAK,IAAI,EACT,MAAM,MAAM,EACZ,MAAM,MAAM,KACX,OAAO,CAAC,MAAM,CAwBhB,CAAA;AAgFD,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,QAAqB,CAAA;AACpD,wEAAwE;AACxE,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAiB,CAAA;AAMxD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,MAAM,EAClB,UAAS;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAM,KAC/D,OAAO,CAAC,WAAW,GAAG,IAAI,CA6C5B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,MAAM,EACZ,KAAK,IAAI,EACT,mBAAmB,MAAM,KACxB,OAAO,CAAC,MAAM,CAmBhB,CAAA;AAED,eAAO,MAAM,WAAW,GAAU,UAAS,kBAAuB,KAAG,OAAO,CAAC,iBAAiB,CAgG7F,CAAA"}