agent-carry 0.4.0 → 0.4.1
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/CHANGELOG.md +24 -9
- package/README.md +4 -4
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +5 -2
- package/plugin/commands/pickup.md +5 -5
- package/plugin/commands/share.md +2 -2
- package/src/cli.js +5 -2
- package/src/desktop.js +84 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plugin, marketplace entry and package share one version number; `npm version` keeps them in step.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 0.4.1
|
|
6
|
+
- `/carry:pickup` no longer asks which account to use or shows ids. `--desktop` picks the account the desktop app is signed in to (read from the app's own config, with the log as a fallback), and in a terminal asks only when the terminal and the desktop app are logged in as different accounts.
|
|
7
|
+
- Accounts are described by name and email where known (the terminal's login), otherwise by the sessions they hold, never by id: `carry desktop-dirs` and the import output now say which account's Code tab got the session.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
Finding sessions without knowing where they are stored. (0.3.0 was tagged but never published to npm.)
|
|
12
|
+
|
|
13
|
+
- `carry list --all` shows the most recent sessions across every folder, newest first, each tagged by where it was started (`[desktop]`, `[terminal]`, `[print mode]`) with the title the desktop app shows. Background-agent sessions (for example a memory plugin's observer) are hidden unless `--everything`.
|
|
14
|
+
- `carry list` options: `--limit N|all`, `--project DIR` (one folder), `--match TEXT` (title, folder or id), `--desktop` / `--terminal` (only sessions started there). Archived desktop sessions are marked; a desktop conversation with no project folder shows as "desktop scratch folder".
|
|
15
|
+
- `carry export` with no id offers a numbered picker that takes the same options.
|
|
16
|
+
- `/carry:share` uses the list; the plugin commands need CLI 0.4.0 or newer.
|
|
17
|
+
|
|
18
|
+
## 0.2.1
|
|
19
|
+
|
|
20
|
+
- Plugin commands check the CLI version first and say how to upgrade if it is older, instead of aborting on an unknown-command error.
|
|
21
|
+
|
|
22
|
+
## 0.2.0
|
|
23
|
+
|
|
24
|
+
- `carry import --desktop` adds the session to the Claude desktop app's Code tab (macOS). It targets the signed-in account (read from the app's log) and works for a freshly signed-in account by borrowing only app-level settings. Terminal colour codes in newly imported text are stripped, since the app shows them as literal characters.
|
|
25
|
+
- `export --latest` skips sessions with no prompts.
|
|
26
|
+
- Interactive plugin commands: `/carry:share` and `/carry:pickup` ask what they need (session, encryption, passphrase source, destination folder, desktop app and account). Installable with `/plugin install carry --marketplace harshhh28/agent-carry`.
|
|
9
27
|
- `export --generate-passphrase` (saved to `FILE.pass`, never printed); `carry bundles`; `carry desktop-dirs`.
|
|
10
|
-
- `--desktop` targets the signed-in account (read from the app's log) instead of any folder with sessions, and works for a freshly signed-in account by borrowing only app-level settings.
|
|
11
28
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- `--desktop` strips terminal colour codes from newly imported text, which the app shows as literal characters.
|
|
15
|
-
- `carry import --desktop` adds the session to the Claude desktop app's Code tab (macOS).
|
|
29
|
+
## 0.1.1
|
|
30
|
+
|
|
16
31
|
- `agent-carry` works as a second command name for `carry`.
|
|
17
32
|
- README trimmed; plugin author set.
|
|
18
33
|
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ carry import session.carry.gz --desktop
|
|
|
42
42
|
App settings are copied from that account's newest Code-tab session (or, for a new account, another account's), so use the Code tab at least once first.
|
|
43
43
|
Terminal colour codes in the imported text are stripped (the app would show them as literal `[38;2;…m`). Restart the desktop app if the session doesn't show.
|
|
44
44
|
|
|
45
|
-
The session is added to the account
|
|
45
|
+
The session is added to the account the desktop app is signed in to. From a terminal that is logged in as a different account, `carry` stops and asks which Code tab you mean, naming the accounts (by name and email where known, otherwise by the sessions they hold). To choose explicitly use `--desktop-dir DIR`; `carry desktop-dirs` lists the accounts. A freshly signed-in account with no sessions borrows only app-level settings from another account, never its connectors or permissions. Cowork is a different store and isn't supported.
|
|
46
46
|
|
|
47
47
|
### Sync through a shared folder
|
|
48
48
|
|
|
@@ -61,7 +61,7 @@ Projects are matched by git remote, not path. Each file has one writer, so there
|
|
|
61
61
|
/plugin install carry --marketplace harshhh28/agent-carry
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Needs `npm i -g agent-carry
|
|
64
|
+
Needs the CLI (`npm i -g agent-carry`, 0.4.0 or newer). Works the same in the terminal and in the desktop app's Code tab. To update later: `npm i -g agent-carry@latest`, then `/plugin marketplace update agent-carry` and `/reload-plugins`.
|
|
65
65
|
|
|
66
66
|
- `/carry:share` asks which session, whether to encrypt (generate a passphrase, use your own file, or type one), and where to save.
|
|
67
67
|
- `/carry:pickup` lists bundles in this folder, ~/Downloads and ~/Desktop, then asks which one, the passphrase source, which folder to import into, and whether to add it to the desktop app (and which account).
|
|
@@ -73,7 +73,7 @@ Passphrases are never put on a command line. A generated one is saved beside the
|
|
|
73
73
|
| command | |
|
|
74
74
|
|---|---|
|
|
75
75
|
| `carry list [--all] [--project DIR] [--desktop\|--terminal] [--match TEXT] [--limit N]` | sessions for this folder, or `--all`: recent sessions from every folder, each tagged by where it was started (`[desktop]`, `[terminal]`, `[print mode]`) with the desktop app's title. `--desktop` / `--terminal` keep only those. Background-agent sessions are hidden; `--everything` shows them |
|
|
76
|
-
| `carry export [id\|--latest] [-o FILE]` | with no id, pick from a numbered list (takes the same `--desktop`, `--terminal`, `--project`, `--match`).
|
|
76
|
+
| `carry export [id\|--latest] [-o FILE]` | with no id, pick from a numbered list (takes the same `--desktop`, `--terminal`, `--project`, `--match`). Also `--generate-passphrase`, `--no-redact`, `--redact-emails`, `--strip-thinking`, `--allow-thinking-secrets`, `--project-root DIR` |
|
|
77
77
|
| `carry inspect FILE` | header, parts, redaction counts |
|
|
78
78
|
| `carry import FILE [--project-root DIR] [--dry-run]` | write into the target folder's session store |
|
|
79
79
|
| `carry bundles` / `carry desktop-dirs` | bundle files nearby; desktop-app account folders |
|
|
@@ -92,7 +92,7 @@ Passphrases are never put on a command line. A generated one is saved beside the
|
|
|
92
92
|
## Development
|
|
93
93
|
|
|
94
94
|
```
|
|
95
|
-
npm test #
|
|
95
|
+
npm test # 56 tests, synthetic two-machine setups
|
|
96
96
|
npm run check:real # read-only round trip of your own ~/.claude sessions
|
|
97
97
|
```
|
|
98
98
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-carry",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Move coding-agent sessions between machines and folders: path-portable, redacted, encrypted, merge-safe bundles. Claude Code today; adapter-ready for Codex and others.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"test": "node --test \"test/*.test.js\"",
|
|
23
|
-
"check:real": "node scripts/check-real.js"
|
|
23
|
+
"check:real": "node scripts/check-real.js",
|
|
24
|
+
"preversion": "npm test",
|
|
25
|
+
"version": "node scripts/sync-version.js && git add plugin/.claude-plugin/plugin.json .claude-plugin/marketplace.json CHANGELOG.md"
|
|
24
26
|
},
|
|
25
27
|
"keywords": [
|
|
26
28
|
"claude-code",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Move coding-agent sessions between machines and folders: /carry:share exports a redacted, path-portable bundle, /carry:pickup imports one.",
|
|
5
|
-
"author": {
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Harsh Gajjar",
|
|
7
|
+
"email": "harshgajjar2812@gmail.com"
|
|
8
|
+
},
|
|
6
9
|
"license": "MIT"
|
|
7
10
|
}
|
|
@@ -4,7 +4,7 @@ argument-hint: [bundle-file]
|
|
|
4
4
|
allowed-tools: Bash(carry:*), Bash(npx -y agent-carry@latest:*), Bash(ls:*), Bash(pwd), AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
carry CLI version (this command needs 0.
|
|
7
|
+
carry CLI version (this command needs 0.4.0 or newer): !`carry --version 2>&1 || echo 'not installed'`
|
|
8
8
|
|
|
9
9
|
Bundle files nearby (this folder, ~/Downloads, ~/Desktop), newest first:
|
|
10
10
|
|
|
@@ -12,13 +12,13 @@ Bundle files nearby (this folder, ~/Downloads, ~/Desktop), newest first:
|
|
|
12
12
|
|
|
13
13
|
Current folder: !`pwd`
|
|
14
14
|
|
|
15
|
-
Claude desktop app
|
|
15
|
+
Claude desktop app accounts (macOS only; an error here just means no desktop app):
|
|
16
16
|
|
|
17
17
|
!`carry desktop-dirs 2>&1 || true`
|
|
18
18
|
|
|
19
19
|
Arguments the user typed: $ARGUMENTS
|
|
20
20
|
|
|
21
|
-
**Step 0.** Look at the version line above. If it says "not installed" or shows anything older than 0.
|
|
21
|
+
**Step 0.** Look at the version line above. If it says "not installed" or shows anything older than 0.4.0, stop and say exactly this, with nothing else: "This command needs the carry CLI 0.4.0 or newer. Run `npm i -g agent-carry@latest`, then try again." (If that install still gives an older version, 0.4.0 has not been published to npm yet.) Any error text shown in the listings below comes from the same cause; do not try to work around it.
|
|
22
22
|
|
|
23
23
|
You are importing one session with the `carry` CLI.
|
|
24
24
|
|
|
@@ -31,6 +31,6 @@ Ask with the AskUserQuestion tool, one short question at a time, and skip any qu
|
|
|
31
31
|
- "I'll type it here": warn that it is stored in this conversation's history. Pass it as `CARRY_PASSPHRASE='…' carry import …` and never repeat it back.
|
|
32
32
|
(If `CARRY_PASSPHRASE` is already set in the environment, nothing is needed.) On a wrong-passphrase error, say so and ask once more.
|
|
33
33
|
3. **Which folder.** Claude Code finds a session by the folder you run `claude` in, so this should be the folder they will work in. Offer the current folder as "(Recommended)" or another path (create it with `mkdir -p` only if the user says so). Pass it as `--project-root`.
|
|
34
|
-
4. **Claude desktop app (only if account folders are listed above).** Ask "Also show it in the Claude desktop app's Code tab?"
|
|
34
|
+
4. **Claude desktop app (only if account folders are listed above).** Ask only "Also show it in the Claude desktop app's Code tab?" Do not ask which account: `--desktop` picks the right one by itself (the account the desktop app is signed in to; in a terminal, the one that matches this terminal's login). Never show the user folder paths or ids. If the import fails with "this terminal is logged in as one Claude account but the desktop app is signed in as another", or "cannot tell which desktop account is signed in", ask once with AskUserQuestion, labelling each option with the account name and email, session count and example titles taken from that error (never ids or paths), and re-run with `--desktop-dir <that option's folder>`. A line starting with `*` in the listing above marks the account the desktop app is signed in to.
|
|
35
35
|
5. **Run** `carry import <bundle> --project-root <folder> [--passphrase-file F] [--desktop | --desktop-dir D]` (add `--dry-run` if the user typed it, and say nothing was written).
|
|
36
|
-
6. **Report in at most five lines:** the session id, the folder it went into, and any warning. Then the next step: exit this session, then `cd <folder> && claude --resume <id>`; for the desktop app, restart it if the session does not appear and open the Code tab. A running session cannot swap its own history, so do not run `claude --resume` yourself.
|
|
36
|
+
6. **Report in at most five lines:** the session id, the folder it went into, which account's Code tab it was added to (use the "Code tab of:" line from the import, e.g. a name and email; say it can be redone for another account if that is wrong), and any warning. Then the next step: exit this session, then `cd <folder> && claude --resume <id>`; for the desktop app, restart it if the session does not appear and open the Code tab. A running session cannot swap its own history, so do not run `claude --resume` yourself.
|
package/plugin/commands/share.md
CHANGED
|
@@ -4,7 +4,7 @@ argument-hint: [optional carry export flags, e.g. -o FILE --strip-thinking]
|
|
|
4
4
|
allowed-tools: Bash(carry:*), Bash(npx -y agent-carry@latest:*), AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
carry CLI version (this command needs 0.
|
|
7
|
+
carry CLI version (this command needs 0.4.0 or newer): !`carry --version 2>&1 || echo 'not installed'`
|
|
8
8
|
|
|
9
9
|
Recent sessions from every folder, newest first, including ones started in the Claude desktop app (the top one is usually this conversation):
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ Current folder: !`pwd`
|
|
|
14
14
|
|
|
15
15
|
Arguments the user typed: $ARGUMENTS
|
|
16
16
|
|
|
17
|
-
**Step 0.** Look at the version line above. If it says "not installed" or shows anything older than 0.
|
|
17
|
+
**Step 0.** Look at the version line above. If it says "not installed" or shows anything older than 0.4.0, stop and say exactly this, with nothing else: "This command needs the carry CLI 0.4.0 or newer. Run `npm i -g agent-carry@latest`, then try again." (If that install still gives an older version, 0.4.0 has not been published to npm yet.) Any error text shown in the listings below comes from the same cause; do not try to work around it.
|
|
18
18
|
|
|
19
19
|
You are exporting one session with the `carry` CLI.
|
|
20
20
|
|
package/src/cli.js
CHANGED
|
@@ -219,7 +219,10 @@ function main() {
|
|
|
219
219
|
|
|
220
220
|
if (cmd === 'desktop-dirs') {
|
|
221
221
|
const list = listDesktopDirs({ home: userHome });
|
|
222
|
-
for (const x of list)
|
|
222
|
+
for (const x of list) {
|
|
223
|
+
const tags = [x.active ? 'signed in to the desktop app' : null, x.cli ? 'this terminal\'s login' : null].filter(Boolean).join(', ');
|
|
224
|
+
process.stdout.write(`${x.active ? '*' : ' '} ${x.label}${tags ? ` [${tags}]` : ''}\n --desktop-dir "${x.dir}"\n`);
|
|
225
|
+
}
|
|
223
226
|
if (!list.length) process.stdout.write('no desktop account folders found\n');
|
|
224
227
|
return;
|
|
225
228
|
}
|
|
@@ -248,7 +251,7 @@ function main() {
|
|
|
248
251
|
const d = registerDesktopSession({ id: plan.id, root: plan.root, mainFile: main.dest, home: userHome, dir: flags['desktop-dir'], dryRun: !!flags['dry-run'] });
|
|
249
252
|
const verb = { registered: 'added to the desktop Code tab', exists: 'already in the desktop Code tab', 'would-register': 'would be added to the desktop Code tab' }[d.status];
|
|
250
253
|
process.stdout.write(` ${verb} (${d.file}); restart the Claude desktop app if it does not appear\n`);
|
|
251
|
-
process.stdout.write(`
|
|
254
|
+
if (d.label) process.stdout.write(` Code tab of: ${d.label}\n`);
|
|
252
255
|
if (d.status === 'exists' && d.entryCwd && d.entryCwd !== plan.root) process.stdout.write(` note: that Code-tab entry points at ${d.entryCwd}, not ${plan.root}\n`);
|
|
253
256
|
process.stdout.write(` chosen as: ${d.how}${d.borrowed ? '; this account has no sessions yet, so app settings were borrowed from another account' : ''}\n`);
|
|
254
257
|
} catch (e) {
|
package/src/desktop.js
CHANGED
|
@@ -59,37 +59,101 @@ export function activeAccountFromLog(home, platform = process.platform) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/**
|
|
62
|
+
/** The account the Claude Code CLI is logged in to, with its readable name, from ~/.claude.json. */
|
|
63
|
+
export function cliAccount(userHome) {
|
|
64
|
+
try {
|
|
65
|
+
const j = JSON.parse(fs.readFileSync(path.join(userHome, '.claude.json'), 'utf8'));
|
|
66
|
+
const a = j.oauthAccount;
|
|
67
|
+
if (!a?.accountUuid) return null;
|
|
68
|
+
return { account: a.accountUuid, org: a.organizationUuid ?? null, name: a.fullName || a.displayName || null, email: a.emailAddress ?? null, orgName: a.organizationName && !(a.emailAddress && a.organizationName.includes(a.emailAddress)) ? a.organizationName : null };
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The account the desktop app is signed in to: its own config.json records the last one. */
|
|
75
|
+
export function desktopSignedInAccount(home, platform = process.platform) {
|
|
76
|
+
if (platform !== 'darwin') return null;
|
|
77
|
+
try {
|
|
78
|
+
const j = JSON.parse(fs.readFileSync(path.join(home, 'Library', 'Application Support', 'Claude', 'config.json'), 'utf8'));
|
|
79
|
+
return typeof j.lastKnownAccountUuid === 'string' ? j.lastKnownAccountUuid : null;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Every <root>/<account>/<org> folder, with a readable label. The app stores accounts by id only, so a name is
|
|
89
|
+
* known for the account this machine's Claude Code CLI is logged in to; any other account is described by how
|
|
90
|
+
* many sessions it holds and a title or two, which is what a person can recognise.
|
|
91
|
+
* `active`: the account the desktop app is signed in to. `cli`: the account the CLI uses.
|
|
92
|
+
*/
|
|
63
93
|
export function listDesktopDirs({ home, platform }) {
|
|
64
94
|
const root = desktopRoot(home, platform);
|
|
65
95
|
if (!fs.existsSync(root)) throw new Error(`no Claude desktop Code-tab data at ${root}; open the Code tab once first`);
|
|
66
|
-
const
|
|
67
|
-
|
|
96
|
+
const me = cliAccount(home);
|
|
97
|
+
const leaves = subdirs(root).flatMap((a) =>
|
|
68
98
|
subdirs(a).map((d) => {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
99
|
+
const entries = readEntries(d);
|
|
100
|
+
const files = entries.length;
|
|
101
|
+
const newest = Math.max(0, ...fs.readdirSync(d).filter((f) => /^local_.*\.json$/.test(f)).map((f) => fs.statSync(path.join(d, f)).mtimeMs));
|
|
102
|
+
const account = path.basename(a);
|
|
103
|
+
const org = path.basename(d);
|
|
104
|
+
const isMe = !!me && me.account === account;
|
|
105
|
+
const titles = entries.filter(({ json }) => json.title && !json.isArchived).sort((x, y) => (y.json.lastActivityAt || 0) - (x.json.lastActivityAt || 0)).map(({ json }) => String(json.title).slice(0, 40)).filter((t, i, all) => all.indexOf(t) === i).slice(0, 2).map((t) => `“${t}”`);
|
|
106
|
+
const who = isMe ? [me.name, me.email && `<${me.email}>`].filter(Boolean).join(' ') || 'This Claude Code login' : 'A different Claude account';
|
|
107
|
+
const orgName = isMe && me.org === org && me.orgName ? ` · ${me.orgName}` : '';
|
|
108
|
+
const label = `${who}${orgName} (${files === 0 ? 'no sessions in the Code tab yet' : `${plural(files, 'session')} in the Code tab`}${titles.length ? `, e.g. ${titles.join(', ')}` : ''})`;
|
|
109
|
+
return { dir: d, account, org, entries: files, newest, cli: isMe, name: isMe ? me.name : null, email: isMe ? me.email : null, label };
|
|
72
110
|
}),
|
|
73
111
|
);
|
|
112
|
+
// The signed-in desktop account (config.json; the app log as a fallback), and within it the org in use.
|
|
113
|
+
const log = activeAccountFromLog(home, platform);
|
|
114
|
+
const acct = desktopSignedInAccount(home, platform) ?? log?.account ?? null;
|
|
115
|
+
let activeDir = null;
|
|
116
|
+
if (acct) {
|
|
117
|
+
const mine = leaves.filter((x) => x.account === acct);
|
|
118
|
+
const pick = (log && log.account === acct && mine.find((x) => x.org === log.org)) || (me && me.account === acct && mine.find((x) => x.org === me.org)) || [...mine].sort((x, y) => y.newest - x.newest)[0];
|
|
119
|
+
activeDir = pick?.dir ?? null;
|
|
120
|
+
}
|
|
121
|
+
return leaves.map((x) => ({ ...x, active: x.dir === activeDir }));
|
|
74
122
|
}
|
|
75
123
|
|
|
76
124
|
/**
|
|
77
|
-
* Which folder to write to
|
|
78
|
-
*
|
|
79
|
-
*
|
|
125
|
+
* Which folder to write to, so nobody has to know account ids:
|
|
126
|
+
* - --desktop-dir if given;
|
|
127
|
+
* - in the desktop app, or when only one account could be meant: the account the desktop app is signed in to;
|
|
128
|
+
* - in a terminal: that same account if the terminal is logged in to it, but if the terminal and the desktop app
|
|
129
|
+
* are logged in as different accounts, ask (the error lists them by name), because a session written into the
|
|
130
|
+
* wrong account's Code tab would never be seen.
|
|
131
|
+
* `surface` is CLAUDE_CODE_ENTRYPOINT: 'cli' for a terminal, 'claude-desktop' for the desktop app.
|
|
80
132
|
*/
|
|
81
|
-
export function resolveDesktopDir({ home, dir, platform }) {
|
|
133
|
+
export function resolveDesktopDir({ home, dir, platform, surface = process.env.CLAUDE_CODE_ENTRYPOINT }) {
|
|
82
134
|
if (dir) {
|
|
83
135
|
if (!fs.existsSync(dir)) throw new Error(`desktop folder not found: ${dir}`);
|
|
84
|
-
return { dir: path.resolve(dir), how: '
|
|
136
|
+
return { dir: path.resolve(dir), how: 'chosen by you', label: null };
|
|
85
137
|
}
|
|
86
138
|
const all = listDesktopDirs({ home, platform });
|
|
87
139
|
if (all.length === 0) throw new Error('no account folder in the desktop app data; open the Code tab once first');
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
140
|
+
const desk = all.find((x) => x.active);
|
|
141
|
+
const mine = all.filter((x) => x.cli);
|
|
142
|
+
const cli = mine.find((x) => x.active) ?? [...mine].sort((x, y) => y.newest - x.newest)[0];
|
|
143
|
+
if (surface === 'cli' && cli && desk && cli.account !== desk.account) {
|
|
144
|
+
const e = new Error(
|
|
145
|
+
`this terminal is logged in as one Claude account but the desktop app is signed in as another; which Code tab should get the session?\n` +
|
|
146
|
+
` 1. ${cli.label} (--desktop-dir "${cli.dir}")\n 2. ${desk.label} (--desktop-dir "${desk.dir}")`,
|
|
147
|
+
);
|
|
148
|
+
e.choices = [cli, desk];
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
const chosen = desk ?? (surface === 'cli' ? cli : null) ?? (all.length === 1 ? all[0] : null);
|
|
152
|
+
if (chosen) return { dir: chosen.dir, how: chosen === desk ? 'the signed-in desktop account' : 'the only account folder', label: chosen.label };
|
|
153
|
+
const rows = all.map((x, i) => ` ${i + 1}. ${x.label} (--desktop-dir "${x.dir}")`).join('\n');
|
|
154
|
+
const e = new Error(`cannot tell which desktop account is signed in; choose one:\n${rows}`);
|
|
155
|
+
e.choices = all;
|
|
156
|
+
throw e;
|
|
93
157
|
}
|
|
94
158
|
|
|
95
159
|
function readEntries(dir) {
|
|
@@ -162,10 +226,10 @@ export function buildDesktopEntry({ template, cliSessionId, root, transcript, no
|
|
|
162
226
|
* Returns { status: 'registered' | 'exists' | 'would-register', file, dir }.
|
|
163
227
|
*/
|
|
164
228
|
export function registerDesktopSession({ id, root, mainFile, home, dir, platform, dryRun = false }) {
|
|
165
|
-
const { dir: target, how } = resolveDesktopDir({ home, dir, platform });
|
|
229
|
+
const { dir: target, how, label } = resolveDesktopDir({ home, dir, platform });
|
|
166
230
|
const existing = readEntries(target);
|
|
167
231
|
const have = existing.find(({ json }) => json.cliSessionId === id || (Array.isArray(json.priorCliSessionIds) && json.priorCliSessionIds.includes(id)));
|
|
168
|
-
if (have) return { status: 'exists', file: have.file, dir: target, how, entryCwd: have.json.cwd };
|
|
232
|
+
if (have) return { status: 'exists', file: have.file, dir: target, how, label, entryCwd: have.json.cwd };
|
|
169
233
|
const newest = (list) => list.filter(({ json }) => !json.isArchived).sort((a, b) => (b.json.lastActivityAt || 0) - (a.json.lastActivityAt || 0))[0]?.json;
|
|
170
234
|
let template = newest(existing);
|
|
171
235
|
let borrowed = false;
|
|
@@ -179,7 +243,7 @@ export function registerDesktopSession({ id, root, mainFile, home, dir, platform
|
|
|
179
243
|
}
|
|
180
244
|
}
|
|
181
245
|
if (!template) throw new Error('no existing Code-tab session anywhere to copy app settings from; start one session in the desktop app Code tab first');
|
|
182
|
-
if (dryRun) return { status: 'would-register', file: 'a new local_*.json', dir: target, how, borrowed };
|
|
246
|
+
if (dryRun) return { status: 'would-register', file: 'a new local_*.json', dir: target, how, label, borrowed };
|
|
183
247
|
|
|
184
248
|
const entry = buildDesktopEntry({ template, cliSessionId: id, root, transcript: parseJsonl(fs.readFileSync(mainFile, 'utf8')) });
|
|
185
249
|
const file = `${entry.sessionId}.json`;
|
|
@@ -187,7 +251,7 @@ export function registerDesktopSession({ id, root, mainFile, home, dir, platform
|
|
|
187
251
|
const tmp = `${dest}.carry-tmp`;
|
|
188
252
|
fs.writeFileSync(tmp, JSON.stringify(entry), { mode: 0o600 });
|
|
189
253
|
fs.renameSync(tmp, dest);
|
|
190
|
-
return { status: 'registered', file, dir: target, how, borrowed };
|
|
254
|
+
return { status: 'registered', file, dir: target, how, label, borrowed };
|
|
191
255
|
}
|
|
192
256
|
|
|
193
257
|
/** cliSessionId -> { title, archived } as the desktop app lists it. Empty when there is no desktop app data. */
|