@topolo/cli 0.13.19 → 0.13.21

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.
Files changed (142) hide show
  1. package/agent-packs/README.md +421 -1
  2. package/agent-packs/docs/selected-artifacts.md +47 -0
  3. package/agent-packs/documentation/cloud-task-state.md +56 -0
  4. package/agent-packs/domain-contexts/README.md +25 -0
  5. package/agent-packs/domain-contexts/cicd.json +8 -0
  6. package/agent-packs/gui/feed-adapter.js +4 -0
  7. package/agent-packs/gui/index.html +9 -0
  8. package/agent-packs/gui/studio.css +14 -0
  9. package/agent-packs/gui/studio.js +121 -0
  10. package/agent-packs/gui/studio_server.py +355 -0
  11. package/agent-packs/roles/cicd/CLAUDE.md +24 -0
  12. package/agent-packs/specializations/cicd--cicd.json +5 -0
  13. package/agent-packs/specializations/cicd--pipeline-engineering.json +5 -0
  14. package/agent-packs/specializations/cicd--promotion-deployment.json +5 -0
  15. package/agent-packs/specializations/cicd--queue-recovery.json +5 -0
  16. package/agent-packs/specializations/cicd--release-verification.json +5 -0
  17. package/agent-packs/specializations/cicd--runner-capacity.json +5 -0
  18. package/agent-packs/specializations/intakes--engineering-02.json +5 -0
  19. package/agent-packs/specializations/intakes--engineering-03.json +5 -0
  20. package/agent-packs/specializations/intakes--engineering.json +5 -0
  21. package/agent-packs/specializations/intakes--operator-01.json +5 -0
  22. package/agent-packs/specializations/intakes--operator-02.json +5 -0
  23. package/agent-packs/specializations/operator-skills--audio-and-narration-specialist.json +5 -0
  24. package/agent-packs/specializations/operator-skills--brand-and-copywriting-specialist.json +5 -0
  25. package/agent-packs/specializations/operator-skills--capability-research-specialist.json +5 -0
  26. package/agent-packs/specializations/operator-skills--data-analysis-and-visualization.json +5 -0
  27. package/agent-packs/specializations/operator-skills--financial-evidence-specialist.json +5 -0
  28. package/agent-packs/specializations/operator-skills--operating-template-specialist.json +5 -0
  29. package/agent-packs/specializations/operator-skills--operator-skill-library.json +5 -0
  30. package/agent-packs/specializations/operator-skills--operator-skills.json +5 -0
  31. package/agent-packs/specializations/operator-skills--operator-workflow-evaluation.json +5 -0
  32. package/agent-packs/specializations/operator-skills--privacy-and-agreement-review.json +5 -0
  33. package/agent-packs/specializations/operator-skills--storytelling-specialist.json +5 -0
  34. package/agent-packs/specializations/operator-skills--ux-and-accessibility-specialist.json +5 -0
  35. package/agent-packs/specializations/operator-skills--video-production-specialist.json +5 -0
  36. package/agent-packs/specializations/operator-skills--visual-design-specialist.json +5 -0
  37. package/agent-packs/specializations/operator-skills--workflow-integration-specialist.json +5 -0
  38. package/agent-packs/specializations/operators--access-and-privacy.json +5 -0
  39. package/agent-packs/specializations/operators--accounting-evidence-review.json +5 -0
  40. package/agent-packs/specializations/operators--agreement-operations.json +5 -0
  41. package/agent-packs/specializations/operators--books-operator.json +5 -0
  42. package/agent-packs/specializations/operators--brand-operator.json +5 -0
  43. package/agent-packs/specializations/operators--commerce-operations.json +5 -0
  44. package/agent-packs/specializations/operators--communications-operator.json +5 -0
  45. package/agent-packs/specializations/operators--content-and-assets-operator.json +5 -0
  46. package/agent-packs/specializations/operators--customer-operations.json +5 -0
  47. package/agent-packs/specializations/operators--design-operator.json +5 -0
  48. package/agent-packs/specializations/operators--design-production-02.json +5 -0
  49. package/agent-packs/specializations/operators--design-production-03.json +5 -0
  50. package/agent-packs/specializations/operators--design-production-04.json +5 -0
  51. package/agent-packs/specializations/operators--device-and-feed-operator.json +5 -0
  52. package/agent-packs/specializations/operators--director-operator.json +5 -0
  53. package/agent-packs/specializations/operators--experience-review.json +5 -0
  54. package/agent-packs/specializations/operators--growth-operations.json +5 -0
  55. package/agent-packs/specializations/operators--learn-operator.json +5 -0
  56. package/agent-packs/specializations/operators--localization-operator.json +5 -0
  57. package/agent-packs/specializations/operators--operators.json +5 -0
  58. package/agent-packs/specializations/operators--pay-operator.json +5 -0
  59. package/agent-packs/specializations/operators--research-and-intelligence.json +5 -0
  60. package/agent-packs/specializations/operators--roadmap-operator.json +5 -0
  61. package/agent-packs/specializations/operators--social-content-operator.json +5 -0
  62. package/agent-packs/specializations/operators--talent-operations.json +5 -0
  63. package/agent-packs/specializations/operators--web-operator.json +5 -0
  64. package/agent-packs/specializations/operators--workflow-operator.json +5 -0
  65. package/agent-packs/specializations/specialists--adversarial-qa.json +5 -0
  66. package/agent-packs/specializations/specialists--backlog-dispatch.json +5 -0
  67. package/agent-packs/specializations/specialists--build-01.json +5 -0
  68. package/agent-packs/specializations/specialists--build-02.json +5 -0
  69. package/agent-packs/specializations/specialists--build-03.json +5 -0
  70. package/agent-packs/specializations/specialists--developer-environment-steward.json +5 -0
  71. package/agent-packs/specializations/specialists--documentation-knowledge-steward.json +5 -0
  72. package/agent-packs/specializations/specialists--execution-readiness.json +5 -0
  73. package/agent-packs/specializations/specialists--incident-triage.json +5 -0
  74. package/agent-packs/specializations/specialists--platform-package-steward.json +5 -0
  75. package/agent-packs/specializations/specialists--product-direction-01.json +5 -0
  76. package/agent-packs/specializations/specialists--product-direction-02.json +5 -0
  77. package/agent-packs/specializations/specialists--release-recovery.json +5 -0
  78. package/agent-packs/specializations/specialists--release-watch.json +5 -0
  79. package/agent-packs/specializations/specialists--roadmap-planning-01.json +5 -0
  80. package/agent-packs/specializations/specialists--roadmap-planning-02.json +5 -0
  81. package/agent-packs/specializations/specialists--security-permissions-review.json +5 -0
  82. package/agent-packs/specializations/specialists--system-architecture-01.json +5 -0
  83. package/agent-packs/specializations/specialists--system-architecture-02.json +5 -0
  84. package/agent-packs/specializations/specialists--translation.json +5 -0
  85. package/agent-packs/specializations/specialists--ux-consistency-review.json +5 -0
  86. package/agent-packs/specializations/specialists--workflow-improvement.json +5 -0
  87. package/agent-packs/specializations/specialists--worktree-retirement.json +5 -0
  88. package/agent-packs/src/topolo_agent_pack/cicd.py +101 -0
  89. package/agent-packs/src/topolo_agent_pack/claude.py +22 -7
  90. package/agent-packs/src/topolo_agent_pack/claude_activation.py +157 -0
  91. package/agent-packs/src/topolo_agent_pack/claude_completion_recovery.py +216 -0
  92. package/agent-packs/src/topolo_agent_pack/claude_desktop.py +181 -0
  93. package/agent-packs/src/topolo_agent_pack/claude_dispatch.py +240 -0
  94. package/agent-packs/src/topolo_agent_pack/claude_prompt.py +74 -0
  95. package/agent-packs/src/topolo_agent_pack/claude_runner.py +207 -0
  96. package/agent-packs/src/topolo_agent_pack/claude_service.py +397 -0
  97. package/agent-packs/src/topolo_agent_pack/cli.py +181 -16
  98. package/agent-packs/src/topolo_agent_pack/closed_ledger_fence.py +118 -0
  99. package/agent-packs/src/topolo_agent_pack/cloud_attach.py +128 -0
  100. package/agent-packs/src/topolo_agent_pack/cloud_cutover.py +134 -0
  101. package/agent-packs/src/topolo_agent_pack/cloud_delta.py +162 -0
  102. package/agent-packs/src/topolo_agent_pack/cloud_migrate.py +142 -0
  103. package/agent-packs/src/topolo_agent_pack/cloud_recovery.py +42 -0
  104. package/agent-packs/src/topolo_agent_pack/cloud_work.py +126 -0
  105. package/agent-packs/src/topolo_agent_pack/coordinator_activity.py +27 -0
  106. package/agent-packs/src/topolo_agent_pack/coordinator_context.py +91 -0
  107. package/agent-packs/src/topolo_agent_pack/coordinator_refresh.py +116 -0
  108. package/agent-packs/src/topolo_agent_pack/desktop_acceptance.py +45 -0
  109. package/agent-packs/src/topolo_agent_pack/desktop_launcher.py +114 -0
  110. package/agent-packs/src/topolo_agent_pack/domain_context.py +38 -0
  111. package/agent-packs/src/topolo_agent_pack/execution.py +103 -6
  112. package/agent-packs/src/topolo_agent_pack/frontdoor.py +23 -0
  113. package/agent-packs/src/topolo_agent_pack/linked_task_context.py +80 -0
  114. package/agent-packs/src/topolo_agent_pack/live.py +285 -18
  115. package/agent-packs/src/topolo_agent_pack/native.py +160 -4
  116. package/agent-packs/src/topolo_agent_pack/review_context.py +33 -0
  117. package/agent-packs/src/topolo_agent_pack/role_catalog.py +137 -0
  118. package/agent-packs/src/topolo_agent_pack/role_context.py +63 -0
  119. package/agent-packs/src/topolo_agent_pack/role_refresh.py +50 -0
  120. package/agent-packs/src/topolo_agent_pack/topolo.py +7 -1
  121. package/agent-packs/src/topolo_agent_pack/work.py +322 -0
  122. package/agent-packs/src/topolo_agent_pack/work_adoption.py +122 -0
  123. package/agent-packs/src/topolo_agent_pack/work_artifacts.py +84 -0
  124. package/agent-packs/src/topolo_agent_pack/work_context.py +77 -0
  125. package/agent-packs/src/topolo_agent_pack/work_gui.py +51 -0
  126. package/agent-packs/src/topolo_agent_pack/work_native.py +414 -0
  127. package/agent-packs/src/topolo_agent_pack/work_wait.py +151 -0
  128. package/agent-packs/templates/topolo-coordinators/AGENTS.md +7 -0
  129. package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/AGENTS.md +6 -0
  130. package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/config.json +4 -0
  131. package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/AGENTS.md +3 -0
  132. package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/config.json +4 -0
  133. package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/AGENTS.md +3 -0
  134. package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/config.json +4 -0
  135. package/agent-packs/templates/topolo-coordinators/pack.json +56 -0
  136. package/dist/chunk-NYDIAYVY.js +144 -0
  137. package/dist/{claude-pack-backend-YEVPAOID.js → chunk-UY5LEU5C.js} +1659 -21
  138. package/dist/claude-desktop-profile-JCQKSY3K.js +13 -0
  139. package/dist/claude-pack-activation-Q25MCEPU.js +506 -0
  140. package/dist/claude-pack-backend-HUTKGIDS.js +90 -0
  141. package/dist/index.js +89 -10
  142. package/package.json +4 -4
@@ -0,0 +1,151 @@
1
+ """Bounded read-only native task following; no dispatcher, retry or model turn."""
2
+ import json
3
+ import os
4
+ import socket
5
+ import stat
6
+ import time
7
+ import uuid
8
+
9
+ from .storage import PackError, canonical, read_json
10
+ from .work import TERMINAL, require
11
+
12
+ LIMIT = 1024 * 1024
13
+
14
+
15
+ def _remaining(deadline):
16
+ remaining = deadline - time.monotonic()
17
+ if remaining <= 0:
18
+ raise TimeoutError('Task wait deadline reached')
19
+ return remaining
20
+
21
+
22
+ def _record(lib):
23
+ path = lib.root / 'live-bridge.json'
24
+ info = path.lstat()
25
+ require(stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid()
26
+ and not info.st_mode & 0o077 and not path.is_symlink(),
27
+ 'Task wait requires the privately owned native connection')
28
+ value = read_json(path)
29
+ require(isinstance(value, dict) and isinstance(value.get('token'), str) and value['token']
30
+ and isinstance(value.get('socket'), str) and os.path.isabs(value['socket']),
31
+ 'Task wait native connection is unavailable')
32
+ info = os.lstat(value['socket'])
33
+ require(stat.S_ISSOCK(info.st_mode) and info.st_uid == os.getuid()
34
+ and not info.st_mode & 0o077, 'Task wait native socket ownership differs')
35
+ return value
36
+
37
+
38
+ class _Connection:
39
+ def __init__(self, lib, record, operation, params, deadline):
40
+ require(_record(lib) == record, 'Native owner changed during task wait; reconnect explicitly')
41
+ self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
42
+ self.buffer = b''
43
+ try:
44
+ self.socket.settimeout(_remaining(deadline))
45
+ self.socket.connect(record['socket'])
46
+ self.socket.settimeout(_remaining(deadline))
47
+ self.socket.sendall(canonical({'token': record['token'], 'operation': operation, 'params': params}) + b'\n')
48
+ except BaseException:
49
+ self.socket.close()
50
+ raise
51
+
52
+ def __enter__(self):
53
+ return self
54
+
55
+ def __exit__(self, *_args):
56
+ self.socket.close()
57
+
58
+ def read(self, deadline):
59
+ while b'\n' not in self.buffer:
60
+ require(len(self.buffer) <= LIMIT, 'Task wait response exceeds its boundary')
61
+ self.socket.settimeout(_remaining(deadline))
62
+ chunk = self.socket.recv(min(65536, LIMIT + 1 - len(self.buffer)))
63
+ require(chunk, 'Native task connection closed before its reply')
64
+ self.buffer += chunk
65
+ line, self.buffer = self.buffer.split(b'\n', 1)
66
+ require(len(line) <= LIMIT, 'Task wait response exceeds its boundary')
67
+ value = json.loads(line)
68
+ require(isinstance(value, dict), 'Native task reply is not an object')
69
+ require('error' not in value, 'Native task read refused: ' + str(value.get('error')))
70
+ return value
71
+
72
+
73
+ def _snapshot(lib, record, task_id, deadline):
74
+ with _Connection(lib, record, 'work-read', {'taskId': task_id}, deadline) as connection:
75
+ value = connection.read(deadline)
76
+ require(set(value) == {'task', 'events'} and isinstance(value['task'], dict)
77
+ and value['task'].get('id') == task_id and isinstance(value['events'], list),
78
+ 'Task wait readback identity differs')
79
+ require(type(value['task'].get('revision')) is int and value['task']['revision'] >= 1,
80
+ 'Task wait revision is invalid')
81
+ events = []
82
+ for event in value['events']:
83
+ require(isinstance(event, dict), 'Task wait event is invalid')
84
+ if event.get('taskId') != task_id:
85
+ continue
86
+ require(type(event.get('sequence')) is int and event['sequence'] > 0,
87
+ 'Task wait event sequence is invalid')
88
+ events.append(event)
89
+ require(len({event['sequence'] for event in events}) == len(events), 'Task event sequence repeats')
90
+ return {'task': value['task'], 'events': sorted(events, key=lambda event: event['sequence'])}
91
+
92
+
93
+ def _result(task_id, snapshot, revision, sequence, timed_out):
94
+ if snapshot is None:
95
+ return {'taskId': task_id, 'task': None, 'events': [],
96
+ 'cursor': {'revision': revision, 'sequence': sequence},
97
+ 'changed': False, 'terminal': False, 'timedOut': True,
98
+ 'snapshotAvailable': False, 'deliveryId': None}
99
+ task, events = snapshot['task'], snapshot['events']
100
+ current = max((event['sequence'] for event in events), default=0)
101
+ require(revision <= task['revision'] and sequence <= current,
102
+ 'Task wait cursor is ahead of the canonical task')
103
+ return {'taskId': task_id, 'task': task,
104
+ 'events': [event for event in events if event['sequence'] > sequence],
105
+ 'cursor': {'revision': task['revision'], 'sequence': current},
106
+ 'changed': task['revision'] > revision or current > sequence,
107
+ 'terminal': task['state'] in TERMINAL, 'timedOut': timed_out,
108
+ 'snapshotAvailable': True,
109
+ 'deliveryId': task_id + ':' + str(task['revision']) + ':' + str(current)}
110
+
111
+
112
+ def wait(lib, value):
113
+ """Follow one existing task within the caller's real native tool turn.
114
+
115
+ Repeat only on an explicit subsequent tool call with the returned cursor.
116
+ A timeout, native cancellation or disconnect never cancels/adopts work.
117
+ """
118
+ require(isinstance(value, dict) and set(value) == {'taskId', 'afterRevision', 'afterSequence', 'timeoutSeconds'},
119
+ 'Exact bounded task wait request required')
120
+ task_id = value['taskId']
121
+ try:
122
+ valid_id = isinstance(task_id, str) and str(uuid.UUID(task_id)) == task_id
123
+ except ValueError:
124
+ valid_id = False
125
+ require(valid_id, 'Task wait requires the exact canonical task UUID')
126
+ revision, sequence, seconds = value['afterRevision'], value['afterSequence'], value['timeoutSeconds']
127
+ require(type(revision) is int and revision >= 0 and type(sequence) is int and sequence >= 0
128
+ and type(seconds) is int and 1 <= seconds <= 30, 'Task wait cursor or deadline is invalid')
129
+ deadline = time.monotonic() + seconds
130
+ record = _record(lib)
131
+ snapshot = None
132
+ try:
133
+ snapshot = _snapshot(lib, record, task_id, deadline)
134
+ result = _result(task_id, snapshot, revision, sequence, False)
135
+ if result['changed'] or result['terminal']:
136
+ return result
137
+ with _Connection(lib, record, 'work-watch', {}, deadline) as connection:
138
+ require(connection.read(deadline) == {'connected': True}, 'Native task watch was not acknowledged')
139
+ # Subscribe first, then re-read. A completion between the first read
140
+ # and watcher registration cannot disappear into a notification gap.
141
+ snapshot = _snapshot(lib, record, task_id, deadline)
142
+ while True:
143
+ result = _result(task_id, snapshot, revision, sequence, False)
144
+ if result['changed'] or result['terminal']:
145
+ return result
146
+ event = connection.read(deadline)
147
+ require(set(event) == {'sequence'} and type(event['sequence']) is int and event['sequence'] >= 0,
148
+ 'Native task notification is invalid')
149
+ snapshot = _snapshot(lib, record, task_id, deadline)
150
+ except TimeoutError:
151
+ return _result(task_id, snapshot, revision, sequence, True)
@@ -0,0 +1,7 @@
1
+ # Topolo task execution rules
2
+
3
+ Load only the current specialization from Topolo Agent and the relevant canonical task and Docs/Bytes packets. This machine's conversation history is not task context. An empty workspace grants no permission to repeat, adopt or resume existing work.
4
+
5
+ Preserve task, claim, owner, intent and checkpoint identities. Respect explicit holds and uncertain effects. Use supported credential-scoped Topolo actions and the governed release controller. Keep credentials out of ordinary context and evidence. Record meaningful outcomes and evidence in the existing task; distinguish reported progress, native settings acknowledgement, execution, independent verification and deployment.
6
+
7
+ The three human front doors acknowledge broker receipts and remain available. Their local specialization charters define that boundary. Temporary task workers execute only their assigned phase and are archived after checkpointing; they do not become reusable lanes. Never load a whole role export, infer completion from a finished model turn, or create schedules or peer-message loops.
@@ -0,0 +1,6 @@
1
+ # Topolo CI/CD
2
+
3
+ Acknowledge the queued request and remain available. If the adapter has not recorded a task, report that dispatch is unavailable; do not take over the implementation. Cloud specialization and task references must be qualified before model work.
4
+
5
+ ## Release-controller operating context
6
+ The controller is the restricted internal service in Topolo-io/TopoloReleaseController, separate from the public application registry and Deploy Orchestrator. Read context/release-controller.md before routing or diagnosis: it is the selected immutable operating runbook, not task history. Current controller access, fleet endpoints and exact intent/job checkpoints belong in the selected task context. Dispatch immediate read-only diagnostics under the existing request; do not ask the human to re-explain the controller or approve an ordinary read. Use the existing broker, preserve owners and record precise missing observer access rather than guessing hosts or creating duplicate work. The submission gateway and native authority observer expose different read operations; follow the bound runbook. Mutations retain their existing release, review and effect gates.
@@ -0,0 +1,4 @@
1
+ {
2
+ "model": "gpt-6.1-sol",
3
+ "reasoningEffort": "low"
4
+ }
@@ -0,0 +1,3 @@
1
+ # Topolo Engineering
2
+
3
+ Acknowledge the queued request and remain available. If the adapter has not recorded a task, report that dispatch is unavailable; do not take over the implementation. Cloud specialization and task references must be qualified before model work.
@@ -0,0 +1,4 @@
1
+ {
2
+ "model": "gpt-6.1-sol",
3
+ "reasoningEffort": "low"
4
+ }
@@ -0,0 +1,3 @@
1
+ # Topolo Operator
2
+
3
+ Acknowledge the queued request and remain available. If the adapter has not recorded a task, report that dispatch is unavailable; do not take over the implementation. Cloud specialization and task references must be qualified before model work.
@@ -0,0 +1,4 @@
1
+ {
2
+ "model": "gpt-6.1-sol",
3
+ "reasoningEffort": "low"
4
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "topolo-coordinators",
4
+ "version": "1.0.0",
5
+ "name": "Topolo task coordinators",
6
+ "purpose": "Three human front doors; specialists are cloud definitions and single-use task workers, not precreated chats.",
7
+ "agents": [
8
+ {
9
+ "set": "intakes",
10
+ "id": "engineering",
11
+ "title": "Topolo Engineering",
12
+ "pinned": true,
13
+ "position": 0,
14
+ "sectionName": "Pinned",
15
+ "aliases": [
16
+ "engineering"
17
+ ]
18
+ },
19
+ {
20
+ "set": "intakes",
21
+ "id": "operator-01",
22
+ "title": "Topolo Operator",
23
+ "pinned": true,
24
+ "position": 1,
25
+ "sectionName": "Pinned",
26
+ "aliases": [
27
+ "operator-01"
28
+ ]
29
+ },
30
+ {
31
+ "set": "intakes",
32
+ "id": "cicd",
33
+ "title": "Topolo CI/CD",
34
+ "pinned": true,
35
+ "position": 2,
36
+ "sectionName": "Pinned",
37
+ "aliases": [
38
+ "cicd"
39
+ ]
40
+ }
41
+ ],
42
+ "dependencies": {
43
+ "applications": [
44
+ "app_topolo_agent",
45
+ "app_topolo_docs",
46
+ "app_topolo_bytes"
47
+ ],
48
+ "capabilities": [
49
+ "production credential-scoped Topolo CLI or MCP",
50
+ "conditional canonical task ownership before dispatch"
51
+ ],
52
+ "integrationNotes": [
53
+ "Authenticate on the new machine, install the cloud role catalog and bind the existing activated task dataset. Never restore conversation history or adopt old workers implicitly."
54
+ ]
55
+ }
56
+ }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/claude-desktop-profile.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { createHash, randomUUID } from "crypto";
7
+ import { spawnSync } from "child_process";
8
+ function launchClaudeDesktopProfile(options, run = spawnSync) {
9
+ const root = fs.realpathSync(options.root), uid = process.getuid?.();
10
+ const ownedDirectory = (value) => {
11
+ const s = fs.lstatSync(value);
12
+ if (!path.isAbsolute(value) || fs.realpathSync(value) !== value || !s.isDirectory() || s.isSymbolicLink() || s.uid !== uid || s.mode & 18) throw Error("Exact owned private Desktop profile directories required.");
13
+ return value;
14
+ };
15
+ ownedDirectory(root);
16
+ const lock = path.join(root, ".claude-profile-launch.lock");
17
+ let lockfd;
18
+ if (options.apply) {
19
+ try {
20
+ lockfd = fs.openSync(lock, "wx", 384);
21
+ fs.writeFileSync(lockfd, JSON.stringify({ pid: process.pid }) + "\n");
22
+ fs.fsyncSync(lockfd);
23
+ } catch {
24
+ throw Error("Managed Desktop launch already has an owner; reconcile without duplicate launch.");
25
+ }
26
+ }
27
+ try {
28
+ const userData = ownedDirectory(options.userData), codeConfig = ownedDirectory(options.codeConfig);
29
+ if (userData === codeConfig || userData.startsWith(codeConfig + path.sep) || codeConfig.startsWith(userData + path.sep)) throw Error("Desktop and SDK profile directories must be distinct.");
30
+ const policy = JSON.parse(fs.readFileSync(options.policy, "utf8"));
31
+ if (policy.format !== "topolo-agent-execution-policy" || policy.pack !== options.pack || policy.installationConsent?.profile !== "full-access" || policy.installationConsent?.principalId === void 0) throw Error("Current reviewed full-access installation policy required for managed Desktop launch.");
32
+ const app = options.app ?? "/Applications/Claude.app", binary = path.join(app, "Contents/MacOS/Claude");
33
+ if (!path.isAbsolute(app) || fs.realpathSync(app) !== app || !fs.lstatSync(app).isDirectory() || !fs.lstatSync(binary).isFile()) throw Error("Installed physical Claude application required.");
34
+ const config = {
35
+ format: "topolo-claude-desktop-profile",
36
+ schemaVersion: 1,
37
+ root,
38
+ pack: options.pack,
39
+ userData,
40
+ codeConfig,
41
+ app,
42
+ consentDigest: createHash("sha256").update(JSON.stringify(policy.installationConsent)).digest("hex")
43
+ };
44
+ const directory = path.join(root, "claude");
45
+ fs.mkdirSync(directory, { recursive: true, mode: 448 });
46
+ ownedDirectory(directory);
47
+ const file = path.join(directory, "desktop-profile.json");
48
+ const read = () => {
49
+ const s = fs.lstatSync(file);
50
+ if (!s.isFile() || s.isSymbolicLink() || s.uid !== uid || s.mode & 63 || s.nlink !== 1) throw Error("Private owned Desktop profile receipt required.");
51
+ return JSON.parse(fs.readFileSync(file, "utf8"));
52
+ };
53
+ const before = fs.existsSync(file) ? read() : null;
54
+ if (before && JSON.stringify(before.profile) !== JSON.stringify(config)) throw Error("Managed Desktop profile identity or installation consent changed.");
55
+ const observed = run("/bin/ps", ["-axo", "pid=,uid=,command="], { encoding: "utf8", timeout: 1e4, maxBuffer: 1048576 });
56
+ if (observed.error || observed.status !== 0) throw Error("Desktop process ownership observation unavailable.");
57
+ const matches = String(observed.stdout).split("\n").map((line) => /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line)).filter(Boolean).filter((m) => m[3].startsWith(binary + " ") && (m[3].endsWith(" --user-data-dir=" + userData) || m[3].includes(" --user-data-dir=" + userData + " --")));
58
+ if (matches.length > 1 || matches.some((m) => Number(m[2]) !== uid)) throw Error("Managed Desktop process ownership is ambiguous.");
59
+ const pid = matches.length ? Number(matches[0][1]) : null;
60
+ if (!options.apply) return { state: "desktop-profile-launch-preflight", profile: config, pid, nativeStartup: false };
61
+ if (before?.state === "launch-may-have-effects" && !pid) throw Error("Unacknowledged Desktop launch requires reconciliation; never launch a duplicate.");
62
+ if (before?.state && !["launch-may-have-effects", "native-profile-verified"].includes(before.state)) throw Error("Unknown Desktop profile launch state.");
63
+ if (pid && before) return { state: "desktop-profile-existing-unverified", profile: config, pid, nativeStartup: false };
64
+ if (!pid && before?.pid) {
65
+ try {
66
+ process.kill(before.pid, 0);
67
+ throw Error("Previous managed process is alive.");
68
+ } catch (error) {
69
+ if (error.code !== "ESRCH") throw Error("Previous managed Desktop process is not independently proven dead.");
70
+ }
71
+ }
72
+ const help = pid ? null : run("/usr/bin/open", ["-h"], { encoding: "utf8", timeout: 1e4, maxBuffer: 65536 });
73
+ if (help && (help.error || !String(help.stdout ?? "").concat(String(help.stderr ?? "")).includes("--env"))) throw Error("LaunchServices per-application environment capability unavailable.");
74
+ const record = { profile: config, operationId: randomUUID(), state: "launch-may-have-effects", history: before ? [...before.history ?? [], { operationId: before.operationId, state: before.state, pid: before.pid ?? null }] : [] };
75
+ const temporary = file + "." + record.operationId + ".tmp";
76
+ const fd = fs.openSync(temporary, "wx", 384);
77
+ try {
78
+ fs.writeFileSync(fd, JSON.stringify(record) + "\n");
79
+ fs.fsyncSync(fd);
80
+ } finally {
81
+ fs.closeSync(fd);
82
+ }
83
+ if (before && JSON.stringify(read()) !== JSON.stringify(before)) throw Error("Desktop profile receipt changed before launch.");
84
+ fs.renameSync(temporary, file);
85
+ const dirfd = fs.openSync(directory, "r");
86
+ try {
87
+ fs.fsyncSync(dirfd);
88
+ } finally {
89
+ fs.closeSync(dirfd);
90
+ }
91
+ if (pid) return { state: "desktop-profile-existing-unverified", profile: config, pid, nativeStartup: false };
92
+ const launched = run("/usr/bin/open", ["-n", "-a", app, "--env", "CLAUDE_CONFIG_DIR=" + codeConfig, "--args", "--user-data-dir=" + userData], { encoding: "utf8", timeout: 2e4, maxBuffer: 65536 });
93
+ if (launched.error || launched.status !== 0) throw Error("Desktop LaunchServices acknowledgement missing; preserve launch receipt without replay.");
94
+ return { state: "desktop-profile-launch-requested", profile: config, operationId: record.operationId, nativeStartup: false };
95
+ } finally {
96
+ if (lockfd !== void 0) {
97
+ fs.closeSync(lockfd);
98
+ fs.unlinkSync(lock);
99
+ }
100
+ }
101
+ }
102
+ function verifyClaudeDesktopProfileNative(expected, pid, app, manager, env) {
103
+ if (!expected || expected.format !== "topolo-claude-desktop-profile" || expected.schemaVersion !== 1 || app.getName() !== "Claude" || app.getPath("userData") !== expected.userData || manager.userDataPath !== expected.userData || env.CLAUDE_CONFIG_DIR !== expected.codeConfig)
104
+ throw Error("Native Desktop profile or SDK history root differs; do not import or recreate identities.");
105
+ return { state: "native-profile-verified", pid, userData: expected.userData, codeConfig: expected.codeConfig };
106
+ }
107
+ function readClaudeDesktopProfile(root) {
108
+ const file = path.join(root, "claude/desktop-profile.json");
109
+ if (!fs.existsSync(file)) return null;
110
+ const s = fs.lstatSync(file);
111
+ if (!s.isFile() || s.isSymbolicLink() || s.uid !== process.getuid?.() || s.mode & 63 || s.nlink !== 1 || s.size > 1048576) throw Error("Private owned Desktop profile receipt required.");
112
+ const record = JSON.parse(fs.readFileSync(file, "utf8"));
113
+ if (record.profile?.root !== root || !["launch-may-have-effects", "native-profile-verified"].includes(record.state)) throw Error("Desktop profile receipt differs.");
114
+ return record;
115
+ }
116
+ function acknowledgeClaudeDesktopProfile(root, before, proof) {
117
+ const file = path.join(root, "claude/desktop-profile.json");
118
+ if (JSON.stringify(readClaudeDesktopProfile(root)) !== JSON.stringify(before) || proof?.state !== "native-profile-verified" || !Number.isSafeInteger(proof.pid) || proof.pid < 1 || proof.userData !== before.profile.userData || proof.codeConfig !== before.profile.codeConfig) throw Error("Native profile acknowledgement or durable custody changed.");
119
+ if (before.state === "native-profile-verified" && before.pid === proof.pid) return before;
120
+ const row = { ...before, state: "native-profile-verified", pid: proof.pid, history: [...before.history ?? [], { operationId: before.operationId, state: before.state, pid: before.pid ?? null }] }, temporary = file + "." + randomUUID() + ".tmp";
121
+ const fd = fs.openSync(temporary, "wx", 384);
122
+ try {
123
+ fs.writeFileSync(fd, JSON.stringify(row) + "\n");
124
+ fs.fsyncSync(fd);
125
+ } finally {
126
+ fs.closeSync(fd);
127
+ }
128
+ if (JSON.stringify(readClaudeDesktopProfile(root)) !== JSON.stringify(before)) throw Error("Desktop profile changed during acknowledgement.");
129
+ fs.renameSync(temporary, file);
130
+ const dir = fs.openSync(path.dirname(file), "r");
131
+ try {
132
+ fs.fsyncSync(dir);
133
+ } finally {
134
+ fs.closeSync(dir);
135
+ }
136
+ return row;
137
+ }
138
+
139
+ export {
140
+ launchClaudeDesktopProfile,
141
+ verifyClaudeDesktopProfileNative,
142
+ readClaudeDesktopProfile,
143
+ acknowledgeClaudeDesktopProfile
144
+ };