@assethub/cli 0.1.32 → 0.1.35
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 +53 -26
- package/dist/agentSetup.d.ts +35 -40
- package/dist/agentSetup.d.ts.map +1 -1
- package/dist/agentSetup.js +64 -110
- package/dist/hooks/command.d.ts +1 -0
- package/dist/hooks/command.d.ts.map +1 -1
- package/dist/hooks/command.js +2 -1
- package/dist/hooks/save.d.ts +14 -0
- package/dist/hooks/save.d.ts.map +1 -1
- package/dist/hooks/save.js +20 -0
- package/dist/hooks/types.d.ts +2 -0
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/upload.d.ts +6 -0
- package/dist/hooks/upload.d.ts.map +1 -1
- package/dist/hooks/upload.js +16 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -43
- package/dist/productionBatch.d.ts +2 -0
- package/dist/productionBatch.d.ts.map +1 -1
- package/dist/productionBatch.js +17 -1
- package/dist/progressReport.d.ts +42 -0
- package/dist/progressReport.d.ts.map +1 -0
- package/dist/progressReport.js +201 -0
- package/dist/setupCommand.d.ts +44 -2
- package/dist/setupCommand.d.ts.map +1 -1
- package/dist/setupCommand.js +313 -131
- package/package.json +1 -1
- package/skills/assethub/SKILL.md +3 -3
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
|
|
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,20 +12,51 @@ 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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
@@ -146,8 +177,7 @@ authentication. Existing workspace keys and user-token login remain supported.
|
|
|
146
177
|
```sh
|
|
147
178
|
assethub --version
|
|
148
179
|
assethub doctor --mcp --profile my-workspace
|
|
149
|
-
assethub
|
|
150
|
-
assethub init
|
|
180
|
+
assethub doctor --setup
|
|
151
181
|
assethub mcp config --client cursor
|
|
152
182
|
assethub mcp config --client codex
|
|
153
183
|
```
|
|
@@ -168,18 +198,8 @@ Use the same key as your CLI profile. Personal profiles include the selected
|
|
|
168
198
|
For a different API origin, pass
|
|
169
199
|
`--base-url https://your-host/prefix` (HTTP is accepted only for localhost).
|
|
170
200
|
|
|
171
|
-
|
|
172
|
-
|
|
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.
|
|
201
|
+
To write these settings into each agent for you, run `assethub setup` (see
|
|
202
|
+
Quick setup above).
|
|
183
203
|
|
|
184
204
|
Explicit `--profile` uses that profile's key and origin, ahead of environment
|
|
185
205
|
variables, including user tokens and workspace MFA proofs. A missing profile fails.
|
|
@@ -378,12 +398,19 @@ assembly was made. Follow a run started without `--wait` with
|
|
|
378
398
|
While a V4 run works, `--wait`, `runs watch` and `runs get` show where it is.
|
|
379
399
|
The receipt's `progress` field has the phase, a one-line summary, each part's
|
|
380
400
|
state and the Blender assembly rounds, in the words of the canvas Part Composer
|
|
381
|
-
card. `--wait` and `runs watch` print a stderr line
|
|
401
|
+
card. `--wait` and `runs watch` print a stderr line, with the local time, only
|
|
402
|
+
when the run really moves on (a new step, another mesh ready, a part kept with
|
|
403
|
+
an issue, the end); part states that come and go do not print:
|
|
382
404
|
|
|
383
405
|
```text
|
|
384
|
-
|
|
406
|
+
19:52 run 557d2a58 3/4 Making the parts · 3 of 9 meshes ready
|
|
407
|
+
19:58 run 557d2a58 ⚠ kept with an issue: Cropped top · open hem at the lower chest
|
|
408
|
+
20:05 run 557d2a58 ✗ FAILED · at 3/4 Making the parts · Blender assembly failed
|
|
385
409
|
```
|
|
386
410
|
|
|
411
|
+
`production batch --wait` prints the same lines per image, named by letter and
|
|
412
|
+
file name, and a status table every 5 minutes; see `docs/guides/production-batch.md`.
|
|
413
|
+
|
|
387
414
|
```sh
|
|
388
415
|
assethub runs watch <run-id> --timeout-ms 7200000 --download --out-dir ./character
|
|
389
416
|
assethub runs get <run-id> # JSON receipt, progress included
|
package/dist/agentSetup.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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';
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
package/dist/agentSetup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentSetup.d.ts","sourceRoot":"","sources":["../src/agentSetup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agentSetup.d.ts","sourceRoot":"","sources":["../src/agentSetup.ts"],"names":[],"mappings":"AAiCA,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,CAAA;AAmCjF,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,CAwBhE,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,eAAe,CAAA;AAkC9E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,IAAI,CAevD,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"}
|
package/dist/agentSetup.js
CHANGED
|
@@ -1,53 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
6
|
-
*
|
|
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
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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 {
|
|
19
|
+
import { env, pid } from 'node:process';
|
|
19
20
|
import { fileURLToPath } from 'node:url';
|
|
20
|
-
import { cliVersion,
|
|
21
|
+
import { cliVersion, validatedBaseUrl } from './setup.js';
|
|
21
22
|
export const AGENT_IDS = ['claude-code', 'codex', 'cursor'];
|
|
22
|
-
const
|
|
23
|
+
export const AGENT_SPECS = [
|
|
23
24
|
{
|
|
24
25
|
id: 'claude-code',
|
|
25
26
|
name: 'Claude Code',
|
|
27
|
+
binary: 'claude',
|
|
26
28
|
detectDir: '.claude',
|
|
27
|
-
globalMcpPath: '.claude.json',
|
|
28
|
-
projectMcpPath: '.mcp.json',
|
|
29
29
|
skillDir: '.claude/skills',
|
|
30
30
|
format: 'claude',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
id: 'codex',
|
|
34
34
|
name: 'Codex',
|
|
35
|
+
binary: 'codex',
|
|
35
36
|
detectDir: '.codex',
|
|
36
|
-
globalMcpPath: '.codex/config.toml',
|
|
37
|
-
projectMcpPath: '.codex/config.toml',
|
|
38
37
|
skillDir: '.codex/skills',
|
|
39
38
|
format: 'codex',
|
|
40
39
|
},
|
|
41
40
|
{
|
|
42
41
|
id: 'cursor',
|
|
43
42
|
name: 'Cursor',
|
|
43
|
+
binary: 'cursor',
|
|
44
44
|
detectDir: '.cursor',
|
|
45
|
-
globalMcpPath: '.cursor/mcp.json',
|
|
46
|
-
projectMcpPath: '.cursor/mcp.json',
|
|
47
45
|
skillDir: '.cursor/skills',
|
|
48
46
|
format: 'cursor',
|
|
49
47
|
},
|
|
50
48
|
];
|
|
49
|
+
export const agentSpec = (id) => AGENT_SPECS.find(agent => agent.id === id);
|
|
50
|
+
/** `--agent` values: repeatable or comma separated; `claude` is accepted for `claude-code`. Keeps AGENT_IDS order. */
|
|
51
|
+
export const parseAgentIds = (values) => {
|
|
52
|
+
const names = values.flatMap(value => value.split(',')).map(value => value.trim().toLowerCase()).filter(Boolean);
|
|
53
|
+
const ids = names.map(name => (name === 'claude' ? 'claude-code' : name));
|
|
54
|
+
const unknown = ids.filter(id => !AGENT_IDS.includes(id));
|
|
55
|
+
if (unknown.length > 0)
|
|
56
|
+
throw new Error(`Unknown agent: ${unknown.join(', ')}. Use --agent ${AGENT_IDS.join('|')}.`);
|
|
57
|
+
return AGENT_IDS.filter(id => ids.includes(id));
|
|
58
|
+
};
|
|
59
|
+
/** Agents installed here: their folder exists under home (or $CODEX_HOME for Codex), or their command is on PATH. */
|
|
60
|
+
export const detectAgents = async (home, findBinary, codexHome) => {
|
|
61
|
+
const found = await Promise.all(AGENT_SPECS.map(async (agent) => (await isDirectory(join(home, agent.detectDir))) ||
|
|
62
|
+
(agent.id === 'codex' && codexHome !== undefined && (await isDirectory(codexHome))) ||
|
|
63
|
+
(await findBinary(agent.binary)) !== undefined));
|
|
64
|
+
return AGENT_SPECS.filter((_, index) => found[index]).map(agent => agent.id);
|
|
65
|
+
};
|
|
51
66
|
const SERVER_NAME = 'assethub';
|
|
52
67
|
const SKILL_NAME = 'assethub';
|
|
53
68
|
const VERSION_MARKER = '.assethub-cli-version';
|
|
@@ -92,16 +107,31 @@ export const mcpServerEntry = (format, baseUrl, workspaceId) => {
|
|
|
92
107
|
},
|
|
93
108
|
};
|
|
94
109
|
};
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
/** The `mcpServers.assethub` entry of a JSON config, or undefined when the file has none or cannot be read. */
|
|
111
|
+
export const readJsonServer = (text) => {
|
|
112
|
+
if (text === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(text);
|
|
116
|
+
return parsed?.mcpServers?.[SERVER_NAME];
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Sets `mcpServers.assethub` in a JSON config and keeps every other key.
|
|
124
|
+
* Returns the new text, or the input unchanged when the entry already matches.
|
|
125
|
+
*/
|
|
126
|
+
export const mergeJsonServer = (path, text, entry) => {
|
|
97
127
|
let document = {};
|
|
98
|
-
if (text !== undefined) {
|
|
128
|
+
if (text !== undefined && text.trim() !== '') {
|
|
99
129
|
let parsed;
|
|
100
130
|
try {
|
|
101
131
|
parsed = JSON.parse(text);
|
|
102
132
|
}
|
|
103
133
|
catch {
|
|
104
|
-
throw new Error(`${path} is not valid JSON. Fix or move it, then rerun
|
|
134
|
+
throw new Error(`${path} is not valid JSON. Fix or move it, then rerun setup.`);
|
|
105
135
|
}
|
|
106
136
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
107
137
|
throw new Error(`${path} must contain a JSON object.`);
|
|
@@ -112,45 +142,11 @@ const writeJsonServer = async (path, entry, dryRun) => {
|
|
|
112
142
|
(typeof document.mcpServers !== 'object' ||
|
|
113
143
|
document.mcpServers === null ||
|
|
114
144
|
Array.isArray(document.mcpServers)))
|
|
115
|
-
throw new Error(`${path} has a non-object "mcpServers" value. Fix or move it, then rerun
|
|
145
|
+
throw new Error(`${path} has a non-object "mcpServers" value. Fix or move it, then rerun setup.`);
|
|
116
146
|
const servers = (document.mcpServers ?? {});
|
|
117
|
-
if (JSON.stringify(servers[SERVER_NAME]) === JSON.stringify(entry))
|
|
118
|
-
return
|
|
119
|
-
|
|
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';
|
|
147
|
+
if (text !== undefined && JSON.stringify(servers[SERVER_NAME]) === JSON.stringify(entry))
|
|
148
|
+
return text;
|
|
149
|
+
return `${JSON.stringify({ ...document, mcpServers: { ...servers, [SERVER_NAME]: entry } }, null, 2)}\n`;
|
|
154
150
|
};
|
|
155
151
|
const readMarker = async (dir) => (await readText(join(dir, VERSION_MARKER)))?.trim();
|
|
156
152
|
/**
|
|
@@ -182,11 +178,11 @@ const replaceSkillDir = async (source, target, version) => {
|
|
|
182
178
|
if (hadTarget)
|
|
183
179
|
await rm(previous, { recursive: true, force: true });
|
|
184
180
|
};
|
|
185
|
-
export const installSkill = async ({
|
|
181
|
+
export const installSkill = async ({ root, version, dryRun, }) => {
|
|
186
182
|
const source = packagedSkillDir();
|
|
187
183
|
if (!(await isDirectory(source)))
|
|
188
184
|
throw new Error('This CLI installation does not include the packaged agent skill.');
|
|
189
|
-
const target = canonicalSkillDir(
|
|
185
|
+
const target = canonicalSkillDir(root);
|
|
190
186
|
const marker = await readMarker(target);
|
|
191
187
|
// A directory without our marker is the user's own; leave it alone.
|
|
192
188
|
const status = marker === undefined
|
|
@@ -259,53 +255,11 @@ export const syncInstalledSkill = async () => {
|
|
|
259
255
|
// best effort
|
|
260
256
|
}
|
|
261
257
|
};
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
};
|
|
258
|
+
/** Installs the skill under `<root>/.agents/skills` and links it into each agent's skill folder under root. */
|
|
259
|
+
export const installAgentSkills = async ({ root, agents, dryRun, }) => {
|
|
260
|
+
const skill = await installSkill({ root, version: await cliVersion(), dryRun });
|
|
261
|
+
const links = [];
|
|
262
|
+
for (const agent of agents)
|
|
263
|
+
links.push({ agent, ...(await linkSkill(join(root, agentSpec(agent).skillDir), skill.path, dryRun)) });
|
|
264
|
+
return { ...skill, links };
|
|
311
265
|
};
|
package/dist/hooks/command.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/hooks/command.js
CHANGED
|
@@ -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
|
package/dist/hooks/save.d.ts
CHANGED
|
@@ -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
|
package/dist/hooks/save.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/hooks/save.js
CHANGED
|
@@ -30,6 +30,8 @@ const spawnCli = (args, profile) => {
|
|
|
30
30
|
};
|
|
31
31
|
/** SessionEnd: upload this session, then retry up to SWEEP_LIMIT older pending ones. */
|
|
32
32
|
export const spawnDetachedUpload = (sessionDir, profile) => spawnCli(['hooks', 'upload', '--session', sessionDir, '--auto', '--sweep', String(SWEEP_LIMIT)], profile);
|
|
33
|
+
/** Stop / PreCompact: upload this running session only; no sweep. */
|
|
34
|
+
export const spawnDetachedSessionUpload = (sessionDir, profile) => spawnCli(['hooks', 'upload', '--session', sessionDir, '--auto'], profile);
|
|
33
35
|
/** SessionStart: retry up to SWEEP_LIMIT pending sessions. */
|
|
34
36
|
export const spawnDetachedSweep = (profile) => spawnCli(['hooks', 'upload', '--pending', '--limit', String(SWEEP_LIMIT)], profile);
|
|
35
37
|
export const retentionDays = (env) => {
|
|
@@ -155,6 +157,13 @@ const collectImages = async (sessionDir, transcript, cwd) => {
|
|
|
155
157
|
export const ABANDONED_AFTER_MS = 2 * 60 * 60 * 1000;
|
|
156
158
|
/** Sessions retried in the background per SessionStart / SessionEnd. */
|
|
157
159
|
export const SWEEP_LIMIT = 3;
|
|
160
|
+
/**
|
|
161
|
+
* A running session is uploaded at most this often. Claude Code app sessions
|
|
162
|
+
* stay open for days and rarely reach SessionEnd, so without this they would
|
|
163
|
+
* only be uploaded once abandoned (ABANDONED_AFTER_MS quiet, then a new session).
|
|
164
|
+
*/
|
|
165
|
+
export const WORKING_UPLOAD_INTERVAL_MS = 20 * 60 * 1000;
|
|
166
|
+
const WORKING_UPLOAD_EVENTS = new Set(['Stop', 'PreCompact']);
|
|
158
167
|
/** Events that copy and redact the transcript now; the rest only mark it changed. */
|
|
159
168
|
const FULL_SAVE_EVENTS = new Set(['SessionEnd', 'Manual']);
|
|
160
169
|
/**
|
|
@@ -312,6 +321,17 @@ export const saveSession = async (options = {}) => {
|
|
|
312
321
|
if (full)
|
|
313
322
|
await materializeSession(sessionDir, { env, transcriptPath });
|
|
314
323
|
const result = { sessionDir, event };
|
|
324
|
+
if (uploadOn && options.startWorkingUpload && WORKING_UPLOAD_EVENTS.has(event)) {
|
|
325
|
+
const latest = await readMeta(sessionDir);
|
|
326
|
+
// Counts uploads any path started (an earlier Stop, SessionEnd, a sweep).
|
|
327
|
+
const lastStart = Math.max(Date.parse(latest?.lastWorkingUploadAt ?? '') || 0, Date.parse(latest?.lastUploadAttemptAt ?? '') || 0);
|
|
328
|
+
if (latest && now.getTime() - lastStart >= WORKING_UPLOAD_INTERVAL_MS) {
|
|
329
|
+
// Stamped before spawning, so the next few turns do not start more.
|
|
330
|
+
await updateMeta(sessionDir, latest, current => ({ ...current, lastWorkingUploadAt: now.toISOString() }));
|
|
331
|
+
options.startWorkingUpload(sessionDir, options.upload?.profile);
|
|
332
|
+
result.uploadStarted = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
315
335
|
if (event === 'SessionEnd') {
|
|
316
336
|
const days = retentionDays(env);
|
|
317
337
|
if (days != null)
|
package/dist/hooks/types.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export type SessionMeta = {
|
|
|
39
39
|
abandoned?: boolean;
|
|
40
40
|
/** How far the graph is compacted after uploads failed for size or time (0 = none). */
|
|
41
41
|
compactLevel?: number;
|
|
42
|
+
/** When Stop or PreCompact last started a background upload of this running session. */
|
|
43
|
+
lastWorkingUploadAt?: string;
|
|
42
44
|
/** Failed uploads in a row; spaces out the automatic retries. */
|
|
43
45
|
failedAttempts?: number;
|
|
44
46
|
/** Size of the live transcript the local copy was made from (before masking). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,CAAA;AAEnE,MAAM,MAAM,YAAY,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAEvF,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,UAAU,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gGAAgG;IAChG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB,CAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,CAAA;AAEnE,MAAM,MAAM,YAAY,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAEvF,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,UAAU,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gGAAgG;IAChG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,wFAAwF;IACxF,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB,CAAA"}
|