@topolo/cli 0.12.33 → 0.13.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/README.md +45 -0
- package/agent-packs/README.md +495 -0
- package/agent-packs/pyproject.toml +16 -0
- package/agent-packs/requirements-test.txt +1 -0
- package/agent-packs/src/topolo_agent_pack/__init__.py +1 -0
- package/agent-packs/src/topolo_agent_pack/blueprint.py +57 -0
- package/agent-packs/src/topolo_agent_pack/claude.py +265 -0
- package/agent-packs/src/topolo_agent_pack/cli.py +443 -0
- package/agent-packs/src/topolo_agent_pack/execution.py +154 -0
- package/agent-packs/src/topolo_agent_pack/fresh.py +158 -0
- package/agent-packs/src/topolo_agent_pack/live.py +403 -0
- package/agent-packs/src/topolo_agent_pack/native.py +645 -0
- package/agent-packs/src/topolo_agent_pack/policy.py +92 -0
- package/agent-packs/src/topolo_agent_pack/storage.py +271 -0
- package/agent-packs/src/topolo_agent_pack/topolo.py +321 -0
- package/agent-packs/templates/career/AGENTS.md +17 -0
- package/agent-packs/templates/career/README.md +14 -0
- package/agent-packs/templates/career/agents/intakes/career-intake/AGENTS.md +21 -0
- package/agent-packs/templates/career/agents/specialists/application-preparation/AGENTS.md +21 -0
- package/agent-packs/templates/career/agents/specialists/application-tracker/AGENTS.md +21 -0
- package/agent-packs/templates/career/agents/specialists/interview-coach/AGENTS.md +21 -0
- package/agent-packs/templates/career/agents/specialists/opportunity-research/AGENTS.md +21 -0
- package/agent-packs/templates/career/agents/specialists/profile-cv/AGENTS.md +21 -0
- package/agent-packs/templates/career/pack.json +124 -0
- package/agent-packs/templates/career/shared/PACK.md +22 -0
- package/agent-packs/templates/career/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/career/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/career/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/clients/AGENTS.md +17 -0
- package/agent-packs/templates/clients/README.md +14 -0
- package/agent-packs/templates/clients/agents/intakes/client-intake/AGENTS.md +21 -0
- package/agent-packs/templates/clients/agents/specialists/acceptance-review/AGENTS.md +21 -0
- package/agent-packs/templates/clients/agents/specialists/delivery-planner/AGENTS.md +21 -0
- package/agent-packs/templates/clients/agents/specialists/discovery/AGENTS.md +21 -0
- package/agent-packs/templates/clients/agents/specialists/proposal-writer/AGENTS.md +21 -0
- package/agent-packs/templates/clients/agents/specialists/solution-design/AGENTS.md +21 -0
- package/agent-packs/templates/clients/pack.json +126 -0
- package/agent-packs/templates/clients/shared/PACK.md +22 -0
- package/agent-packs/templates/clients/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/clients/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/clients/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/creative/AGENTS.md +17 -0
- package/agent-packs/templates/creative/README.md +14 -0
- package/agent-packs/templates/creative/agents/intakes/creative-director/AGENTS.md +21 -0
- package/agent-packs/templates/creative/agents/specialists/brand-copy/AGENTS.md +21 -0
- package/agent-packs/templates/creative/agents/specialists/media-production/AGENTS.md +21 -0
- package/agent-packs/templates/creative/agents/specialists/quality-review/AGENTS.md +21 -0
- package/agent-packs/templates/creative/agents/specialists/storytelling/AGENTS.md +21 -0
- package/agent-packs/templates/creative/agents/specialists/visual-design/AGENTS.md +21 -0
- package/agent-packs/templates/creative/pack.json +128 -0
- package/agent-packs/templates/creative/shared/PACK.md +22 -0
- package/agent-packs/templates/creative/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/creative/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/creative/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/founder/AGENTS.md +17 -0
- package/agent-packs/templates/founder/README.md +14 -0
- package/agent-packs/templates/founder/agents/intakes/founder-intake/AGENTS.md +21 -0
- package/agent-packs/templates/founder/agents/specialists/commercial-modelling/AGENTS.md +21 -0
- package/agent-packs/templates/founder/agents/specialists/customer-research/AGENTS.md +21 -0
- package/agent-packs/templates/founder/agents/specialists/decision-review/AGENTS.md +21 -0
- package/agent-packs/templates/founder/agents/specialists/partnerships/AGENTS.md +21 -0
- package/agent-packs/templates/founder/agents/specialists/strategy/AGENTS.md +21 -0
- package/agent-packs/templates/founder/pack.json +126 -0
- package/agent-packs/templates/founder/shared/PACK.md +22 -0
- package/agent-packs/templates/founder/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/founder/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/founder/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/learning/AGENTS.md +17 -0
- package/agent-packs/templates/learning/README.md +14 -0
- package/agent-packs/templates/learning/agents/intakes/learning-intake/AGENTS.md +21 -0
- package/agent-packs/templates/learning/agents/specialists/critical-reviewer/AGENTS.md +21 -0
- package/agent-packs/templates/learning/agents/specialists/curriculum-designer/AGENTS.md +21 -0
- package/agent-packs/templates/learning/agents/specialists/knowledge-steward/AGENTS.md +21 -0
- package/agent-packs/templates/learning/agents/specialists/practice-coach/AGENTS.md +21 -0
- package/agent-packs/templates/learning/agents/specialists/tutor/AGENTS.md +21 -0
- package/agent-packs/templates/learning/pack.json +124 -0
- package/agent-packs/templates/learning/shared/PACK.md +22 -0
- package/agent-packs/templates/learning/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/learning/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/learning/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/personal/AGENTS.md +17 -0
- package/agent-packs/templates/personal/README.md +14 -0
- package/agent-packs/templates/personal/agents/intakes/personal-intake/AGENTS.md +21 -0
- package/agent-packs/templates/personal/agents/specialists/correspondence/AGENTS.md +21 -0
- package/agent-packs/templates/personal/agents/specialists/document-assistant/AGENTS.md +21 -0
- package/agent-packs/templates/personal/agents/specialists/planning/AGENTS.md +21 -0
- package/agent-packs/templates/personal/agents/specialists/research/AGENTS.md +21 -0
- package/agent-packs/templates/personal/agents/specialists/travel-logistics/AGENTS.md +21 -0
- package/agent-packs/templates/personal/pack.json +125 -0
- package/agent-packs/templates/personal/shared/PACK.md +22 -0
- package/agent-packs/templates/personal/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/personal/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/personal/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/research/AGENTS.md +17 -0
- package/agent-packs/templates/research/README.md +14 -0
- package/agent-packs/templates/research/agents/intakes/research-intake/AGENTS.md +21 -0
- package/agent-packs/templates/research/agents/specialists/adversarial-review/AGENTS.md +21 -0
- package/agent-packs/templates/research/agents/specialists/data-analysis/AGENTS.md +21 -0
- package/agent-packs/templates/research/agents/specialists/source-research/AGENTS.md +21 -0
- package/agent-packs/templates/research/agents/specialists/synthesis/AGENTS.md +21 -0
- package/agent-packs/templates/research/agents/specialists/technical-research/AGENTS.md +21 -0
- package/agent-packs/templates/research/pack.json +124 -0
- package/agent-packs/templates/research/shared/PACK.md +22 -0
- package/agent-packs/templates/research/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/research/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/research/skills/topolo/SKILL.md +305 -0
- package/agent-packs/templates/ventures/AGENTS.md +17 -0
- package/agent-packs/templates/ventures/README.md +14 -0
- package/agent-packs/templates/ventures/agents/intakes/venture-intake/AGENTS.md +21 -0
- package/agent-packs/templates/ventures/agents/specialists/customer-discovery/AGENTS.md +21 -0
- package/agent-packs/templates/ventures/agents/specialists/economics/AGENTS.md +21 -0
- package/agent-packs/templates/ventures/agents/specialists/experiment-design/AGENTS.md +21 -0
- package/agent-packs/templates/ventures/agents/specialists/market-research/AGENTS.md +21 -0
- package/agent-packs/templates/ventures/agents/specialists/product-concept/AGENTS.md +21 -0
- package/agent-packs/templates/ventures/pack.json +126 -0
- package/agent-packs/templates/ventures/shared/PACK.md +22 -0
- package/agent-packs/templates/ventures/shared/TOPOLO.md +13 -0
- package/agent-packs/templates/ventures/shared/WORKFLOW.md +13 -0
- package/agent-packs/templates/ventures/skills/topolo/SKILL.md +305 -0
- package/agent-packs/topolo-agent-pack +8 -0
- package/dist/claude-pack-backend-IDQQX3DG.js +555 -0
- package/dist/index.js +143 -58
- package/package.json +7 -4
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Versioned Claude Code Desktop session import. Never rewrites live app records.
|
|
2
|
+
|
|
3
|
+
Fresh role files and CLI transcripts are owned by this adapter. Desktop adopts
|
|
4
|
+
them through its native resume handoff. There is no external archive API in
|
|
5
|
+
that handoff; full roster replacement is therefore refused, not simulated.
|
|
6
|
+
"""
|
|
7
|
+
import argparse
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import pathlib
|
|
12
|
+
import pty
|
|
13
|
+
import select as io_select
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
import uuid
|
|
19
|
+
|
|
20
|
+
from .storage import Library, PackError, atomic_json, canonical, now, safe_id, select
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_binary():
|
|
24
|
+
root = pathlib.Path.home() / 'Library/Application Support/Claude/claude-code'
|
|
25
|
+
candidates = list(root.glob('*/*/claude.app/Contents/MacOS/claude'))
|
|
26
|
+
if candidates:
|
|
27
|
+
return str(max(candidates, key=lambda p: tuple(int(x) for x in p.relative_to(root).parts[0].split('.'))))
|
|
28
|
+
return shutil.which('claude')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def handoff(binary, directory, session_id):
|
|
32
|
+
"""Native --desktop requires a TTY. No prompt, model call or shell is used."""
|
|
33
|
+
if not binary:
|
|
34
|
+
raise PackError('Claude Code 2.1.285+ is required; install Claude Desktop or Claude Code.')
|
|
35
|
+
version = subprocess.run([binary, '--version'], capture_output=True, text=True, timeout=10)
|
|
36
|
+
if version.returncode or not version.stdout.startswith('2.1.289 '):
|
|
37
|
+
raise PackError('This native import adapter is qualified for Claude Code 2.1.289; selected binary: ' + version.stdout.strip())
|
|
38
|
+
master, slave = pty.openpty()
|
|
39
|
+
process = None
|
|
40
|
+
output = bytearray()
|
|
41
|
+
try:
|
|
42
|
+
process = subprocess.Popen([binary, '--desktop', '--resume', session_id],
|
|
43
|
+
cwd=directory, stdin=slave, stdout=slave, stderr=slave)
|
|
44
|
+
os.close(slave)
|
|
45
|
+
slave = None
|
|
46
|
+
deadline = time.monotonic() + 20
|
|
47
|
+
while process.poll() is None:
|
|
48
|
+
if time.monotonic() >= deadline:
|
|
49
|
+
raise PackError('Claude Desktop handoff timed out; run recover to reconcile imports.')
|
|
50
|
+
if io_select.select([master], [], [], .1)[0]:
|
|
51
|
+
try:
|
|
52
|
+
chunk = os.read(master, 65536)
|
|
53
|
+
except OSError:
|
|
54
|
+
break
|
|
55
|
+
output.extend(chunk)
|
|
56
|
+
if len(output) > 65536:
|
|
57
|
+
raise PackError('Claude Desktop handoff exceeded its output limit.')
|
|
58
|
+
process.wait(timeout=2)
|
|
59
|
+
if process.returncode or ('Opening session ' + session_id) not in output.decode(errors='replace'):
|
|
60
|
+
raise PackError('Claude refused the native Desktop handoff: ' + output.decode(errors='replace')[-2000:])
|
|
61
|
+
finally:
|
|
62
|
+
if process is not None and process.poll() is None:
|
|
63
|
+
process.terminate()
|
|
64
|
+
process.wait(timeout=5)
|
|
65
|
+
if slave is not None:
|
|
66
|
+
os.close(slave)
|
|
67
|
+
os.close(master)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def project_directory(config, directory):
|
|
71
|
+
# The native CLI's current path-key codec. A versioned compatibility boundary.
|
|
72
|
+
import re
|
|
73
|
+
key = re.sub(r'[^a-zA-Z0-9]', '-', str(directory))
|
|
74
|
+
if len(key) > 200:
|
|
75
|
+
raise PackError('Claude project path exceeds the qualified path-key boundary (200 characters).')
|
|
76
|
+
return config / 'projects' / key
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def write_new(path, data):
|
|
80
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
81
|
+
if path.exists() or path.is_symlink():
|
|
82
|
+
if path.is_symlink() or not path.is_file() or path.read_bytes() != data:
|
|
83
|
+
raise PackError('Refusing to overwrite existing context or transcript: ' + str(path))
|
|
84
|
+
return
|
|
85
|
+
with path.open('xb') as stream:
|
|
86
|
+
stream.write(data)
|
|
87
|
+
stream.flush()
|
|
88
|
+
os.fsync(stream.fileno())
|
|
89
|
+
path.chmod(0o600)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def materialize(lib, name, selected, config):
|
|
93
|
+
manifest, revision = lib.load(name)
|
|
94
|
+
if manifest.get('startingContext', {}).get('kind') != 'fresh':
|
|
95
|
+
raise PackError('Claude import requires a fresh pack with reviewed role files; old Codex history is not translated.')
|
|
96
|
+
records = []
|
|
97
|
+
root = lib.root / 'claude'
|
|
98
|
+
for agent in selected:
|
|
99
|
+
safe_id(agent['set'])
|
|
100
|
+
safe_id(agent['id'])
|
|
101
|
+
key = name + '/' + agent['set'] + '/' + agent['id']
|
|
102
|
+
identity = root / 'identities' / (hashlib.sha256(key.encode()).hexdigest() + '.json')
|
|
103
|
+
if identity.exists():
|
|
104
|
+
record = json.loads(identity.read_text())
|
|
105
|
+
if record['sourceRevision'] != revision:
|
|
106
|
+
raise PackError('Existing Claude agent uses a different revision; build a new named pack to preserve its context.')
|
|
107
|
+
if record['importState'] != 'preparing':
|
|
108
|
+
records.append(record)
|
|
109
|
+
continue
|
|
110
|
+
else:
|
|
111
|
+
session_id = str(uuid.uuid4())
|
|
112
|
+
workspace = root / 'workspaces' / name / agent['set'] / agent['id']
|
|
113
|
+
record = {'key': key, 'pack': name, 'set': agent['set'], 'agent': agent['id'],
|
|
114
|
+
'title': agent['title'], 'pinnedRequested': agent.get('pinned', False),
|
|
115
|
+
'sourceRevision': revision, 'sourceCodexThreadId': agent['threadId'],
|
|
116
|
+
'cliSessionId': session_id, 'desktopSessionId': 'local_' + session_id,
|
|
117
|
+
'workspace': str(workspace),
|
|
118
|
+
'transcript': str(project_directory(config, workspace) / (session_id + '.jsonl')),
|
|
119
|
+
'importState': 'preparing', 'createdAt': now(), 'bootstrapUuid': str(uuid.uuid4())}
|
|
120
|
+
# Save allocation before any workspace/transcript write. Retrying a
|
|
121
|
+
# interrupted preparation uses the same IDs and compares old bytes.
|
|
122
|
+
atomic_json(identity, record)
|
|
123
|
+
original = pathlib.Path(manifest['threads'][agent['threadId']]['cwd'])
|
|
124
|
+
workspace = pathlib.Path(record['workspace'])
|
|
125
|
+
if workspace.is_symlink():
|
|
126
|
+
raise PackError('Claude workspace must not be a symlink')
|
|
127
|
+
workspace.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
128
|
+
resources = [r for r in manifest['resources'] if pathlib.Path(r['original']).is_relative_to(original)]
|
|
129
|
+
instructions = next((r for r in resources if pathlib.Path(r['original']).relative_to(original).as_posix() == 'AGENTS.md'), None)
|
|
130
|
+
if instructions is None:
|
|
131
|
+
raise PackError('Missing reviewed role instructions for ' + key)
|
|
132
|
+
for resource in resources:
|
|
133
|
+
relative = pathlib.Path(resource['original']).relative_to(original)
|
|
134
|
+
if '..' in relative.parts or any(p.is_symlink() for p in (workspace / relative).parents if p != lib.root.parent):
|
|
135
|
+
raise PackError('Unsafe context path: ' + str(relative))
|
|
136
|
+
# Never copy runtime state, prior tasks, scripts or credentials into a role.
|
|
137
|
+
if relative.suffix != '.md' or relative.as_posix() == 'context/TEAM.md':
|
|
138
|
+
continue
|
|
139
|
+
if relative.as_posix() == 'AGENTS.md':
|
|
140
|
+
relative = pathlib.Path('CLAUDE.md')
|
|
141
|
+
write_new(workspace / relative, lib.object(resource['content']).read_bytes())
|
|
142
|
+
session_id = record['cliSessionId']
|
|
143
|
+
transcript = pathlib.Path(record['transcript'])
|
|
144
|
+
note = (agent['title'] + '. You are this Topolo Agent Pack role in Claude Desktop. '
|
|
145
|
+
'Read CLAUDE.md and relevant context/ files before work. '
|
|
146
|
+
'This is starting context only; no assignment, claim or prior conversation is inherited. '
|
|
147
|
+
'Await a new human request. Coordination requires human authorization.')
|
|
148
|
+
rows = [{'parentUuid': None, 'isSidechain': False, 'userType': 'external',
|
|
149
|
+
'cwd': str(workspace), 'sessionId': session_id, 'version': '2.1.289',
|
|
150
|
+
'type': 'user', 'message': {'role': 'user', 'content': note},
|
|
151
|
+
'uuid': record['bootstrapUuid'], 'timestamp': record['createdAt']},
|
|
152
|
+
{'type': 'custom-title', 'sessionId': session_id, 'customTitle': agent['title']}]
|
|
153
|
+
write_new(transcript, b''.join(canonical(row) + b'\n' for row in rows))
|
|
154
|
+
record['importState'] = 'prepared'
|
|
155
|
+
atomic_json(identity, record)
|
|
156
|
+
records.append(record)
|
|
157
|
+
team = ('# Claude pack roster\n\nThese are Claude Desktop session identities. '
|
|
158
|
+
'No task, claim, messaging authorization or hosted connection is granted by this roster.\n\n' +
|
|
159
|
+
'\n'.join('- ' + a['key'] + ' | ' + a['title'] + ' | ' + a['desktopSessionId'] for a in records) + '\n')
|
|
160
|
+
for record in records:
|
|
161
|
+
context = pathlib.Path(record['workspace']) / 'context'
|
|
162
|
+
write_new(context / ('TEAM-' + hashlib.sha256(team.encode()).hexdigest()[:16] + '.md'), team.encode())
|
|
163
|
+
pointer = context / 'TEAM.md'
|
|
164
|
+
if not pointer.exists():
|
|
165
|
+
write_new(pointer, b'# Claude team identities\n\nRead TEAM-*.md for immutable Claude roster snapshots. Use topolo-agent-pack status --harness claude for current membership. Only human-authorized coordination is permitted.\n')
|
|
166
|
+
return records
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def readback(records, app_data):
|
|
170
|
+
results = []
|
|
171
|
+
for record in records:
|
|
172
|
+
files = list((app_data / 'claude-code-sessions').glob('*/*/' + record['desktopSessionId'] + '.json'))
|
|
173
|
+
if len(files) != 1:
|
|
174
|
+
results.append({'key': record['key'], 'verified': False, 'reason': 'unique Desktop adoption record missing'})
|
|
175
|
+
continue
|
|
176
|
+
state = json.loads(files[0].read_text())
|
|
177
|
+
ok = (state.get('cliSessionId') == record['cliSessionId'] and
|
|
178
|
+
state.get('title') == record['title'] and
|
|
179
|
+
state.get('cwd') == record['workspace'] and not state.get('isArchived'))
|
|
180
|
+
results.append({'key': record['key'], 'desktopSessionId': record['desktopSessionId'],
|
|
181
|
+
'verified': ok, 'completedTurns': state.get('completedTurns') or 0,
|
|
182
|
+
'record': str(files[0])})
|
|
183
|
+
return results
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def parser():
|
|
187
|
+
p = argparse.ArgumentParser(description='Claude Code Desktop pack sessions (native sidebar import; full switch is not yet supported).')
|
|
188
|
+
p.add_argument('--store', default=os.environ.get('CODEX_PACK_HOME', str(pathlib.Path.home() / 'AgentPacks')))
|
|
189
|
+
p.add_argument('--claude-config', default=str(pathlib.Path.home() / '.claude'))
|
|
190
|
+
p.add_argument('--claude-data', default=str(pathlib.Path.home() / 'Library/Application Support/Claude'))
|
|
191
|
+
sub = p.add_subparsers(dest='command', required=True)
|
|
192
|
+
for action in ['add', 'switch', 'replace']:
|
|
193
|
+
q = sub.add_parser(action)
|
|
194
|
+
q.add_argument('source')
|
|
195
|
+
q.add_argument('--set', '--group', dest='sets', action='append')
|
|
196
|
+
q.add_argument('--agent', dest='agents', action='append')
|
|
197
|
+
q.add_argument('--catalog', action='store_true')
|
|
198
|
+
q.add_argument('--no-open', action='store_true')
|
|
199
|
+
q.add_argument('--import-only', action='store_true', help='Explicit incomplete native session import; does not activate pack settings')
|
|
200
|
+
q.add_argument('--claude-bin', default=find_binary())
|
|
201
|
+
for action in ['status', 'doctor', 'recover', 'list']:
|
|
202
|
+
sub.add_parser(action)
|
|
203
|
+
return p
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def main(argv):
|
|
207
|
+
args = parser().parse_args(argv)
|
|
208
|
+
try:
|
|
209
|
+
lib = Library(args.store)
|
|
210
|
+
app_data = pathlib.Path(args.claude_data).expanduser().resolve()
|
|
211
|
+
state_path = lib.root / 'claude/state.json'
|
|
212
|
+
with lib.lock():
|
|
213
|
+
state = json.loads(state_path.read_text()) if state_path.exists() else {'schemaVersion': 1, 'harness': 'claude', 'agents': []}
|
|
214
|
+
if args.command in {'status', 'doctor', 'recover', 'list'}:
|
|
215
|
+
proof = readback(state['agents'], app_data)
|
|
216
|
+
active_path = lib.root / 'claude/active.json'
|
|
217
|
+
pending_path = lib.root / 'claude/native-operation.json'
|
|
218
|
+
result = {**state, 'adoptionProof': proof, 'verifiedAgents': sum(p['verified'] for p in proof),
|
|
219
|
+
'fullPackSwitchSupported': False, 'sidebarGroupsAndPinsVerified': False,
|
|
220
|
+
'nativeRecordsRewritten': False, 'modelIdentityResponseVerified': False,
|
|
221
|
+
'activationReady': False, 'modelAndPermissionSettingsVerified': False,
|
|
222
|
+
'lastVerifiedActiveRoster': json.loads(active_path.read_text()) if active_path.exists() else None,
|
|
223
|
+
'nativeRecoveryRequired': pending_path.exists(),
|
|
224
|
+
'nativeRecoveryReplayed': False,
|
|
225
|
+
'verificationBoundary': 'read-only adoption records; use the explicit native connection for current settings or recovery'}
|
|
226
|
+
else:
|
|
227
|
+
if (lib.root / 'claude/native-operation.json').exists():
|
|
228
|
+
raise PackError('An unresolved native Claude operation blocks imports and preparation. Use topolo agent-packs recover with the explicit native connection; no effects are replayed.')
|
|
229
|
+
manifest, revision = lib.load(args.source)
|
|
230
|
+
chosen = select(manifest, args.sets, args.agents)
|
|
231
|
+
keys = {args.source + '/' + a['set'] + '/' + a['id'] for a in chosen}
|
|
232
|
+
outgoing = {a['key'] for a in state['agents']} - keys
|
|
233
|
+
if args.command in {'switch', 'replace'} and outgoing:
|
|
234
|
+
raise PackError('Claude full-pack replacement is not qualified: native --desktop --resume cannot archive the outgoing roster. No changes were made. Use add for sidebar import.')
|
|
235
|
+
if not args.no_open and not args.import_only:
|
|
236
|
+
raise PackError('Claude pack activation requires a qualified live backend for groups, pins, model and permission settings. No changes were made. --no-open prepares context; --import-only explicitly imports sessions without completing pack activation.')
|
|
237
|
+
records = materialize(lib, args.source, chosen, pathlib.Path(args.claude_config).expanduser().resolve())
|
|
238
|
+
combined = {a['key']: a for a in state['agents']}
|
|
239
|
+
combined.update({a['key']: a for a in records})
|
|
240
|
+
state = {**state, 'agents': list(combined.values()), 'updatedAt': now()}
|
|
241
|
+
# Durable recovery plan before asking Desktop to adopt a session.
|
|
242
|
+
atomic_json(state_path, state)
|
|
243
|
+
if not args.no_open:
|
|
244
|
+
for record in records:
|
|
245
|
+
if not readback([record], app_data)[0]['verified']:
|
|
246
|
+
handoff(args.claude_bin, record['workspace'], record['cliSessionId'])
|
|
247
|
+
deadline = time.monotonic() + 15
|
|
248
|
+
while not readback([record], app_data)[0]['verified']:
|
|
249
|
+
if time.monotonic() > deadline:
|
|
250
|
+
raise PackError('Desktop adoption not verified for ' + record['key'] + '; run recover.')
|
|
251
|
+
time.sleep(.1)
|
|
252
|
+
record['importState'] = 'desktop-adopted'
|
|
253
|
+
atomic_json(state_path, state)
|
|
254
|
+
proof = readback(records, app_data)
|
|
255
|
+
result = {'harness': 'claude', 'state': 'prepared' if args.no_open else 'desktop-adopted',
|
|
256
|
+
'selectedAgents': len(records), 'verifiedAgents': sum(p['verified'] for p in proof),
|
|
257
|
+
'adoptionProof': proof, 'fullPackSwitchSupported': False,
|
|
258
|
+
'previousCodexHistoryCopied': False, 'nativeRecordsRewritten': False,
|
|
259
|
+
'sidebarGroupsAndPinsVerified': False, 'activationReady': False,
|
|
260
|
+
'modelAndPermissionSettingsVerified': False}
|
|
261
|
+
print(json.dumps(result, indent=2))
|
|
262
|
+
return 0
|
|
263
|
+
except (PackError, OSError, ValueError, KeyError, subprocess.SubprocessError) as exc:
|
|
264
|
+
print('topolo-agent-pack (Claude): ' + str(exc), file=sys.stderr)
|
|
265
|
+
return 2
|