@synap-core/cli 1.12.2 → 1.13.0

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 (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -8
  3. package/dist/build-info.json +2 -2
  4. package/dist/commands/agents.d.ts +0 -1
  5. package/dist/commands/agents.js +1 -1
  6. package/dist/commands/agents.js.map +1 -1
  7. package/dist/commands/app.js +2 -1
  8. package/dist/commands/app.js.map +1 -1
  9. package/dist/commands/automation.js +46 -41
  10. package/dist/commands/automation.js.map +1 -1
  11. package/dist/commands/box.d.ts +42 -0
  12. package/dist/commands/box.js +89 -0
  13. package/dist/commands/box.js.map +1 -0
  14. package/dist/commands/bridge-setup.js +3 -1
  15. package/dist/commands/bridge-setup.js.map +1 -1
  16. package/dist/commands/capability.d.ts +1 -1
  17. package/dist/commands/capability.js +57 -34
  18. package/dist/commands/capability.js.map +1 -1
  19. package/dist/commands/close-questions.d.ts +27 -0
  20. package/dist/commands/close-questions.js +49 -0
  21. package/dist/commands/close-questions.js.map +1 -0
  22. package/dist/commands/close-questions.test.d.ts +1 -0
  23. package/dist/commands/close-questions.test.js +52 -0
  24. package/dist/commands/close-questions.test.js.map +1 -0
  25. package/dist/commands/connect.d.ts +1 -3
  26. package/dist/commands/connect.js +2 -2
  27. package/dist/commands/connect.js.map +1 -1
  28. package/dist/commands/context.js +72 -43
  29. package/dist/commands/context.js.map +1 -1
  30. package/dist/commands/data-extra.js +12 -4
  31. package/dist/commands/data-extra.js.map +1 -1
  32. package/dist/commands/data.js +2 -1
  33. package/dist/commands/data.js.map +1 -1
  34. package/dist/commands/doctor.d.ts +21 -0
  35. package/dist/commands/doctor.js +108 -13
  36. package/dist/commands/doctor.js.map +1 -1
  37. package/dist/commands/hooks.d.ts +19 -0
  38. package/dist/commands/hooks.js +70 -0
  39. package/dist/commands/hooks.js.map +1 -0
  40. package/dist/commands/import.d.ts +25 -0
  41. package/dist/commands/import.js +98 -12
  42. package/dist/commands/import.js.map +1 -1
  43. package/dist/commands/init.d.ts +14 -0
  44. package/dist/commands/init.js +59 -19
  45. package/dist/commands/init.js.map +1 -1
  46. package/dist/commands/knowledge.js +4 -2
  47. package/dist/commands/knowledge.js.map +1 -1
  48. package/dist/commands/lens.js +2 -1
  49. package/dist/commands/lens.js.map +1 -1
  50. package/dist/commands/market-authoring.js +1 -1
  51. package/dist/commands/market-authoring.js.map +1 -1
  52. package/dist/commands/mcp.js +2 -2
  53. package/dist/commands/mcp.js.map +1 -1
  54. package/dist/commands/observe.category.test.d.ts +1 -0
  55. package/dist/commands/observe.category.test.js +59 -0
  56. package/dist/commands/observe.category.test.js.map +1 -0
  57. package/dist/commands/observe.d.ts +3 -0
  58. package/dist/commands/observe.js +7 -5
  59. package/dist/commands/observe.js.map +1 -1
  60. package/dist/commands/observe.title.test.d.ts +1 -0
  61. package/dist/commands/observe.title.test.js +37 -0
  62. package/dist/commands/observe.title.test.js.map +1 -0
  63. package/dist/commands/playbook.js +9 -0
  64. package/dist/commands/playbook.js.map +1 -1
  65. package/dist/commands/pods.d.ts +6 -1
  66. package/dist/commands/pods.js +32 -15
  67. package/dist/commands/pods.js.map +1 -1
  68. package/dist/commands/proton-setup.js +3 -1
  69. package/dist/commands/proton-setup.js.map +1 -1
  70. package/dist/commands/session-feed-wait.d.ts +48 -0
  71. package/dist/commands/session-feed-wait.js +261 -0
  72. package/dist/commands/session-feed-wait.js.map +1 -0
  73. package/dist/commands/session-post.d.ts +40 -0
  74. package/dist/commands/session-post.js +96 -0
  75. package/dist/commands/session-post.js.map +1 -0
  76. package/dist/commands/session-wait.d.ts +9 -4
  77. package/dist/commands/session-wait.js +20 -5
  78. package/dist/commands/session-wait.js.map +1 -1
  79. package/dist/commands/sessions.d.ts +2 -0
  80. package/dist/commands/sessions.js +23 -0
  81. package/dist/commands/sessions.js.map +1 -1
  82. package/dist/commands/skills-commands.d.ts +2 -0
  83. package/dist/commands/skills-commands.js +9 -1
  84. package/dist/commands/skills-commands.js.map +1 -1
  85. package/dist/commands/skills-commands.test.d.ts +1 -0
  86. package/dist/commands/skills-commands.test.js +57 -0
  87. package/dist/commands/skills-commands.test.js.map +1 -0
  88. package/dist/commands/status.js +1 -1
  89. package/dist/commands/status.js.map +1 -1
  90. package/dist/commands/template.d.ts +42 -0
  91. package/dist/commands/template.js +191 -0
  92. package/dist/commands/template.js.map +1 -0
  93. package/dist/commands/tracks.d.ts +67 -6
  94. package/dist/commands/tracks.js +268 -16
  95. package/dist/commands/tracks.js.map +1 -1
  96. package/dist/commands/whoami.d.ts +7 -0
  97. package/dist/commands/whoami.js +17 -0
  98. package/dist/commands/whoami.js.map +1 -1
  99. package/dist/commands/worker-accounts.d.ts +149 -0
  100. package/dist/commands/worker-accounts.js +339 -0
  101. package/dist/commands/worker-accounts.js.map +1 -0
  102. package/dist/commands/worker-accounts.test.d.ts +1 -0
  103. package/dist/commands/worker-accounts.test.js +224 -0
  104. package/dist/commands/worker-accounts.test.js.map +1 -0
  105. package/dist/commands/worker.d.ts +219 -10
  106. package/dist/commands/worker.js +749 -117
  107. package/dist/commands/worker.js.map +1 -1
  108. package/dist/commands/worker.test.js +321 -10
  109. package/dist/commands/worker.test.js.map +1 -1
  110. package/dist/index.js +164 -27
  111. package/dist/index.js.map +1 -1
  112. package/dist/lib/agent-door.d.ts +1 -3
  113. package/dist/lib/agent-door.js +10 -7
  114. package/dist/lib/agent-door.js.map +1 -1
  115. package/dist/lib/auth.d.ts +3 -2
  116. package/dist/lib/auth.js +3 -3
  117. package/dist/lib/auth.js.map +1 -1
  118. package/dist/lib/box/egress.d.ts +82 -0
  119. package/dist/lib/box/egress.js +264 -0
  120. package/dist/lib/box/egress.js.map +1 -0
  121. package/dist/lib/box/evidence.d.ts +86 -0
  122. package/dist/lib/box/evidence.js +121 -0
  123. package/dist/lib/box/evidence.js.map +1 -0
  124. package/dist/lib/box/job-server.d.ts +72 -0
  125. package/dist/lib/box/job-server.js +246 -0
  126. package/dist/lib/box/job-server.js.map +1 -0
  127. package/dist/lib/box/provisioner.d.ts +83 -0
  128. package/dist/lib/box/provisioner.js +312 -0
  129. package/dist/lib/box/provisioner.js.map +1 -0
  130. package/dist/lib/box/proxmox.d.ts +112 -0
  131. package/dist/lib/box/proxmox.js +211 -0
  132. package/dist/lib/box/proxmox.js.map +1 -0
  133. package/dist/lib/box/wire.d.ts +98 -0
  134. package/dist/lib/box/wire.js +70 -0
  135. package/dist/lib/box/wire.js.map +1 -0
  136. package/dist/lib/browser-auth.d.ts +35 -0
  137. package/dist/lib/browser-auth.js +170 -0
  138. package/dist/lib/browser-auth.js.map +1 -0
  139. package/dist/lib/client-header.test.d.ts +1 -0
  140. package/dist/lib/client-header.test.js +85 -0
  141. package/dist/lib/client-header.test.js.map +1 -0
  142. package/dist/lib/conversation-headers.d.ts +24 -9
  143. package/dist/lib/conversation-headers.js +40 -3
  144. package/dist/lib/conversation-headers.js.map +1 -1
  145. package/dist/lib/credential-class.d.ts +28 -1
  146. package/dist/lib/credential-class.js +28 -6
  147. package/dist/lib/credential-class.js.map +1 -1
  148. package/dist/lib/exporter-coverage.d.ts +1 -1
  149. package/dist/lib/exporter-coverage.js +1 -1
  150. package/dist/lib/goal-words.d.ts +38 -0
  151. package/dist/lib/goal-words.js +112 -0
  152. package/dist/lib/goal-words.js.map +1 -0
  153. package/dist/lib/hub-client.d.ts +28 -7
  154. package/dist/lib/hub-client.js +75 -34
  155. package/dist/lib/hub-client.js.map +1 -1
  156. package/dist/lib/init-handoff.d.ts +8 -0
  157. package/dist/lib/init-handoff.js +9 -2
  158. package/dist/lib/init-handoff.js.map +1 -1
  159. package/dist/lib/key-source.d.ts +3 -1
  160. package/dist/lib/key-source.js +7 -4
  161. package/dist/lib/key-source.js.map +1 -1
  162. package/dist/lib/knowledge-title.d.ts +3 -2
  163. package/dist/lib/knowledge-title.js +3 -2
  164. package/dist/lib/knowledge-title.js.map +1 -1
  165. package/dist/lib/mcp-bridge.d.ts +7 -0
  166. package/dist/lib/mcp-bridge.js +34 -1
  167. package/dist/lib/mcp-bridge.js.map +1 -1
  168. package/dist/lib/mcp-bridge.test.js +31 -3
  169. package/dist/lib/mcp-bridge.test.js.map +1 -1
  170. package/dist/lib/openclaw.d.ts +32 -0
  171. package/dist/lib/openclaw.js +165 -0
  172. package/dist/lib/openclaw.js.map +1 -0
  173. package/dist/lib/pod-moved.d.ts +70 -0
  174. package/dist/lib/pod-moved.js +146 -0
  175. package/dist/lib/pod-moved.js.map +1 -0
  176. package/dist/lib/pod-reach.d.ts +39 -0
  177. package/dist/lib/pod-reach.js +117 -0
  178. package/dist/lib/pod-reach.js.map +1 -0
  179. package/dist/lib/pod-reach.test.d.ts +1 -0
  180. package/dist/lib/pod-reach.test.js +67 -0
  181. package/dist/lib/pod-reach.test.js.map +1 -0
  182. package/dist/lib/pod.d.ts +8 -0
  183. package/dist/lib/pod.js +12 -3
  184. package/dist/lib/pod.js.map +1 -1
  185. package/dist/lib/register-claude-code-mcp.test.d.ts +1 -0
  186. package/dist/lib/register-claude-code-mcp.test.js +78 -0
  187. package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
  188. package/dist/lib/review-credential.js +3 -1
  189. package/dist/lib/review-credential.js.map +1 -1
  190. package/dist/lib/seed.d.ts +13 -0
  191. package/dist/lib/seed.js +135 -0
  192. package/dist/lib/seed.js.map +1 -0
  193. package/dist/lib/session-feed.d.ts +185 -0
  194. package/dist/lib/session-feed.js +257 -0
  195. package/dist/lib/session-feed.js.map +1 -0
  196. package/dist/lib/targets.d.ts +21 -3
  197. package/dist/lib/targets.js +52 -107
  198. package/dist/lib/targets.js.map +1 -1
  199. package/dist/lib/wake-hook-settings.d.ts +57 -0
  200. package/dist/lib/wake-hook-settings.js +153 -0
  201. package/dist/lib/wake-hook-settings.js.map +1 -0
  202. package/dist/lib/wake-state.d.ts +65 -0
  203. package/dist/lib/wake-state.js +122 -0
  204. package/dist/lib/wake-state.js.map +1 -0
  205. package/package.json +5 -2
  206. package/skills/README.md +1 -1
  207. package/skills/agent-os/SKILL.md +8 -8
  208. package/skills/synap/SKILL.md +141 -84
  209. package/skills/synap/code-skills.md +70 -0
  210. package/skills/synap/concepts.md +28 -20
  211. package/skills/synap/creative-loop.md +1 -1
  212. package/skills/synap/document-embeds.md +3 -1
  213. package/skills/synap/focus-sessions.md +16 -7
  214. package/skills/synap/from-intent.md +32 -16
  215. package/skills/synap/governance-rules.md +1 -1
  216. package/skills/synap/lenses.md +4 -4
  217. package/skills/synap/linking.md +2 -2
  218. package/skills/synap/mental-model.md +1 -1
  219. package/skills/synap/operating-mode-rules.md +1 -1
  220. package/skills/synap/quick-reference.md +2 -1
  221. package/skills/synap/reflexes.md +6 -6
  222. package/skills/synap/viewframe-cells.md +1 -1
  223. package/skills/synap/work-model.md +24 -10
  224. package/skills/synap/workspace-design.md +5 -5
  225. package/skills/synap/writes.md +16 -9
  226. package/skills/synap-app/SKILL.md +148 -66
  227. package/skills/synap-app/creating-the-base-app.md +12 -7
  228. package/skills/synap-app/governance.md +27 -16
  229. package/skills/synap-app/install-commands.md +18 -9
  230. package/skills/synap-app/overview.md +16 -9
  231. package/skills/synap-app/using-the-sdk.md +75 -25
  232. package/skills/synap-schema/SKILL.md +21 -21
  233. package/skills/synap-schema/intent-vocabulary-ssot.md +21 -21
  234. package/templates/box/runner.py +206 -0
@@ -28,7 +28,7 @@
28
28
  * blocked), classification is deliberately three-valued and the guard is
29
29
  * belt-and-braces:
30
30
  * • `agent` — positive agent signal → refuse locally, before the network call.
31
- * • `human` — positive human signal (`user_pat`) → never blocked.
31
+ * • `human` — positive human signal (`user_pat` NOT owned by an agent user) → never blocked.
32
32
  * • `unknown` — no signal → PROCEED, and translate the server's 403 after the
33
33
  * fact (`isAgentReviewRejection`). The server stays authoritative.
34
34
  *
@@ -58,9 +58,36 @@ export interface AuthStatus {
58
58
  isActive?: boolean;
59
59
  keyType?: string | null;
60
60
  workspaceId?: string | null;
61
+ /**
62
+ * What the key may touch — its grant (pod ≥ owner-app-connect-key; absent
63
+ * on older pods, null when the pod read no grant for the request).
64
+ */
65
+ access?: KeyAccess | null;
66
+ }
67
+ /** `/auth/status` `access`: the calling key's grant, ids and patterns only. */
68
+ export interface KeyAccess {
69
+ isFullAccess: boolean;
70
+ /** "full access", or what limits it ("limited to 1 space (<id>)"). */
71
+ summary: string;
72
+ scopes: Array<{
73
+ permissions: string[];
74
+ workspaceIds: string[] | null;
75
+ projectIds: string[] | null;
76
+ entityIds: string[] | null;
77
+ }>;
78
+ clientId: string | null;
61
79
  }
62
80
  /** Introspect a key. `apiKey` overrides `cfg.apiKey` (used by `whoami` to probe both sides of a divergence). */
63
81
  export declare function fetchAuthStatus(cfg: HubConfig, apiKey?: string): Promise<AuthStatus>;
82
+ /**
83
+ * WHO this key is — the agent principal, for recognising the agent's OWN
84
+ * posts (a room message carries it as `routedTeammateId`). Never the acting
85
+ * user sent to the pod: that is `resolveUserId` (/users/me), which for an
86
+ * agent key answers the linked HUMAN. The id recorded with the key
87
+ * (`cfg.agentUserId`) wins; else the pod's key-owner answer (/auth/status),
88
+ * cached per key. For a person's own key both questions answer the person.
89
+ */
90
+ export declare function resolveAgentUserId(cfg: HubConfig): Promise<string>;
64
91
  /**
65
92
  * Shape of `GET /api/hub/users/me` (`hub-protocol/rest/users.ts:36-46`).
66
93
  *
@@ -28,7 +28,7 @@
28
28
  * blocked), classification is deliberately three-valued and the guard is
29
29
  * belt-and-braces:
30
30
  * • `agent` — positive agent signal → refuse locally, before the network call.
31
- * • `human` — positive human signal (`user_pat`) → never blocked.
31
+ * • `human` — positive human signal (`user_pat` NOT owned by an agent user) → never blocked.
32
32
  * • `unknown` — no signal → PROCEED, and translate the server's 403 after the
33
33
  * fact (`isAgentReviewRejection`). The server stays authoritative.
34
34
  *
@@ -47,6 +47,27 @@ import { hubGet, HubError } from "./hub-client.js";
47
47
  export async function fetchAuthStatus(cfg, apiKey) {
48
48
  return (await hubGet("/auth/status", {}, apiKey ? { ...cfg, apiKey } : cfg));
49
49
  }
50
+ const agentUserIdCache = new Map();
51
+ /**
52
+ * WHO this key is — the agent principal, for recognising the agent's OWN
53
+ * posts (a room message carries it as `routedTeammateId`). Never the acting
54
+ * user sent to the pod: that is `resolveUserId` (/users/me), which for an
55
+ * agent key answers the linked HUMAN. The id recorded with the key
56
+ * (`cfg.agentUserId`) wins; else the pod's key-owner answer (/auth/status),
57
+ * cached per key. For a person's own key both questions answer the person.
58
+ */
59
+ export async function resolveAgentUserId(cfg) {
60
+ if (cfg.agentUserId)
61
+ return cfg.agentUserId;
62
+ const cached = agentUserIdCache.get(cfg.apiKey);
63
+ if (cached)
64
+ return cached;
65
+ const owner = (await fetchAuthStatus(cfg)).userId;
66
+ if (!owner)
67
+ throw new Error("The pod did not say who owns this key (GET /auth/status has no userId).");
68
+ agentUserIdCache.set(cfg.apiKey, owner);
69
+ return owner;
70
+ }
50
71
  /** Resolve the effective identity. `apiKey` overrides `cfg.apiKey`, mirroring `fetchAuthStatus`. */
51
72
  export async function fetchEffectiveIdentity(cfg, apiKey) {
52
73
  return (await hubGet("/users/me", {}, apiKey ? { ...cfg, apiKey } : cfg));
@@ -56,14 +77,15 @@ const AGENT_EMAIL_DOMAIN = "@synap.agent";
56
77
  export function classifyReviewer(status) {
57
78
  if (!status)
58
79
  return "unknown";
59
- // A personal access token is a HUMAN session by construction — it carries no
60
- // linkedUserId, so `rejectAgentReviewer` never fires for it. Check this FIRST
61
- // so a human key can never be caught by the email heuristic below.
62
- if (status.keyType === "user_pat")
63
- return "human";
80
+ // The agent domain FIRST: the server derives the agent from the key owner's
81
+ // `userType === 'agent'` (access/key-identity.ts), whatever the key type, and
82
+ // only agent users carry the synthetic domain. The device flow mints
83
+ // `user_pat` keys owned by a machine agent, so keyType alone is not human.
64
84
  const email = (status.userEmail ?? "").toLowerCase();
65
85
  if (email.endsWith(AGENT_EMAIL_DOMAIN))
66
86
  return "agent";
87
+ if (status.keyType === "user_pat")
88
+ return "human";
67
89
  return "unknown";
68
90
  }
69
91
  /** Best-effort classification. A pod that is down/older must not block the command — it degrades to `unknown`. */
@@ -1 +1 @@
1
- {"version":3,"file":"credential-class.js","sourceRoot":"","sources":["../../src/lib/credential-class.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"credential-class.js","sourceRoot":"","sources":["../../src/lib/credential-class.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * Loops stay here deliberately: there is no durable loop row to reverse-
16
16
  * serialise (`createLoopFromDefinition` dissolves into playbooks + triggers).
17
17
  *
18
- * `TEMPLATE-DEV-GUIDE.md` used to assert, unqualified, that a
18
+ * The template guide used to assert, unqualified, that a
19
19
  * `PackageDefinition` "round-trips". It does not, in this direction, and the
20
20
  * consequence is the shape this repo keeps paying for: an author who built one
21
21
  * of the things below publishes their workspace, ships a thinner-than-expected
@@ -15,7 +15,7 @@
15
15
  * Loops stay here deliberately: there is no durable loop row to reverse-
16
16
  * serialise (`createLoopFromDefinition` dissolves into playbooks + triggers).
17
17
  *
18
- * `TEMPLATE-DEV-GUIDE.md` used to assert, unqualified, that a
18
+ * The template guide used to assert, unqualified, that a
19
19
  * `PackageDefinition` "round-trips". It does not, in this direction, and the
20
20
  * consequence is the shape this repo keeps paying for: an author who built one
21
21
  * of the things below publishes their workspace, ships a thinner-than-expected
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A GOAL's words as `synap track` prints them — status, verdict, kind of
3
+ * work, uncertainty, criterion marks, what it waits on, who is on it, the
4
+ * end state's label.
5
+ *
6
+ * `@synap-core/types/vocabulary` is the one door for these words, but this
7
+ * package cannot import it (the CLI links only `@synap-core/hub-rest-client`
8
+ * and `@synap-core/workspace-templates`). So each table here is a COPY of the
9
+ * vocabulary's own, held EQUAL — keys and words — by
10
+ * `test/goal-words-parity.test.ts`, which reads the vocabulary's source in
11
+ * the sibling `synap-backend`. Delete this file for a plain import once the
12
+ * CLI depends on a published `@synap-core/types` carrying them.
13
+ */
14
+ /** `TRACK_STATUS_LABELS` */
15
+ export declare const TRACK_STATUS_LABELS: Readonly<Record<string, string>>;
16
+ /** `TRACK_VERDICT_LABELS` */
17
+ export declare const TRACK_VERDICT_LABELS: Readonly<Record<string, string>>;
18
+ /** `TRACK_WORK_KIND_LABELS` */
19
+ export declare const TRACK_WORK_KIND_LABELS: Readonly<Record<string, string>>;
20
+ /** `TRACK_UNCERTAINTY_LABELS` */
21
+ export declare const TRACK_UNCERTAINTY_LABELS: Readonly<Record<string, string>>;
22
+ /** `GOAL_CRITERION_MARK_LABELS` */
23
+ export declare const GOAL_CRITERION_MARK_LABELS: Readonly<Record<string, string>>;
24
+ /** `WAITING_ON_REASON_LABELS` */
25
+ export declare const WAITING_ON_REASON_LABELS: Readonly<Record<string, string>>;
26
+ /** `TRACK_PARTICIPANT_ROLE_LABELS` (vocabulary/track-participant-words.ts) */
27
+ export declare const TRACK_PARTICIPANT_ROLE_LABELS: Readonly<Record<string, string>>;
28
+ /** `TRACK_COMMITMENT_STATE_LABELS` (vocabulary/track-participant-words.ts) */
29
+ export declare const TRACK_COMMITMENT_STATE_LABELS: Readonly<Record<string, string>>;
30
+ /** `GOAL_FIELD_LABELS` — the goal's stored fields as a person reads them. */
31
+ export declare const GOAL_FIELD_LABELS: Readonly<Record<string, string>>;
32
+ /**
33
+ * The vocabulary's fallback (`humanizeToken`, minus its cased-words list): a
34
+ * value no table names reads as words, never as the raw token.
35
+ */
36
+ export declare function humanizeToken(token: string): string;
37
+ /** A token through its table, else humanized — `""` for none. */
38
+ export declare function goalWord(table: Readonly<Record<string, string>>, token: string | null | undefined): string;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * A GOAL's words as `synap track` prints them — status, verdict, kind of
3
+ * work, uncertainty, criterion marks, what it waits on, who is on it, the
4
+ * end state's label.
5
+ *
6
+ * `@synap-core/types/vocabulary` is the one door for these words, but this
7
+ * package cannot import it (the CLI links only `@synap-core/hub-rest-client`
8
+ * and `@synap-core/workspace-templates`). So each table here is a COPY of the
9
+ * vocabulary's own, held EQUAL — keys and words — by
10
+ * `test/goal-words-parity.test.ts`, which reads the vocabulary's source in
11
+ * the sibling `synap-backend`. Delete this file for a plain import once the
12
+ * CLI depends on a published `@synap-core/types` carrying them.
13
+ */
14
+ /** `TRACK_STATUS_LABELS` */
15
+ export const TRACK_STATUS_LABELS = {
16
+ proposed: "Proposed",
17
+ agreed: "Agreed",
18
+ ready: "Ready",
19
+ active: "Active",
20
+ paused: "Paused",
21
+ completed: "Closed",
22
+ archived: "Dropped",
23
+ };
24
+ /** `TRACK_VERDICT_LABELS` */
25
+ export const TRACK_VERDICT_LABELS = {
26
+ met: "Met",
27
+ partly_met: "Partly met",
28
+ not_met: "Not met",
29
+ retired: "Retired",
30
+ no_longer_needed: "No longer needed",
31
+ impossible: "Impossible",
32
+ replaced: "Replaced",
33
+ mistake: "Created by mistake",
34
+ };
35
+ /** `TRACK_WORK_KIND_LABELS` */
36
+ export const TRACK_WORK_KIND_LABELS = {
37
+ project: "Project",
38
+ flow: "Flow",
39
+ operation: "Operation",
40
+ investigation: "Investigation",
41
+ incident: "Incident",
42
+ commission: "Commission",
43
+ pathway: "Pathway",
44
+ };
45
+ /** `TRACK_UNCERTAINTY_LABELS` */
46
+ export const TRACK_UNCERTAINTY_LABELS = {
47
+ clear: "Clear",
48
+ complicated: "Complicated",
49
+ complex: "Complex",
50
+ chaotic: "Chaotic",
51
+ unknown: "Unknown",
52
+ };
53
+ /** `GOAL_CRITERION_MARK_LABELS` */
54
+ export const GOAL_CRITERION_MARK_LABELS = {
55
+ met: "Met",
56
+ claimed: "Claimed",
57
+ under_way: "Under way",
58
+ not_met: "Not met",
59
+ not_started: "Not started",
60
+ };
61
+ /** `WAITING_ON_REASON_LABELS` */
62
+ export const WAITING_ON_REASON_LABELS = {
63
+ input: "Input",
64
+ approval: "Approval",
65
+ access: "Access",
66
+ another_work: "Other work",
67
+ outside_world: "Outside world",
68
+ };
69
+ /** `TRACK_PARTICIPANT_ROLE_LABELS` (vocabulary/track-participant-words.ts) */
70
+ export const TRACK_PARTICIPANT_ROLE_LABELS = {
71
+ owner: "Owner",
72
+ judge: "Judge",
73
+ delegate: "Delegate",
74
+ integrator: "Integrator",
75
+ };
76
+ /** `TRACK_COMMITMENT_STATE_LABELS` (vocabulary/track-participant-words.ts) */
77
+ export const TRACK_COMMITMENT_STATE_LABELS = {
78
+ proposed: "Asked",
79
+ accepted: "Accepted",
80
+ countered: "Countered",
81
+ declined: "Declined",
82
+ reported_impossible: "Reported impossible",
83
+ done: "Done",
84
+ };
85
+ /** `GOAL_FIELD_LABELS` — the goal's stored fields as a person reads them. */
86
+ export const GOAL_FIELD_LABELS = {
87
+ endState: "What done looks like",
88
+ criteria: "Done when",
89
+ workKind: "Kind of work",
90
+ uncertainty: "How well the way is known",
91
+ dropConditions: "Drop it if",
92
+ };
93
+ /**
94
+ * The vocabulary's fallback (`humanizeToken`, minus its cased-words list): a
95
+ * value no table names reads as words, never as the raw token.
96
+ */
97
+ export function humanizeToken(token) {
98
+ const tail = token.includes(".") ? token.slice(token.lastIndexOf(".") + 1) : token;
99
+ const words = tail
100
+ .replace(/[_-]+/g, " ")
101
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
102
+ .trim()
103
+ .toLowerCase();
104
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : "";
105
+ }
106
+ /** A token through its table, else humanized — `""` for none. */
107
+ export function goalWord(table, token) {
108
+ if (!token)
109
+ return "";
110
+ return Object.hasOwn(table, token) ? table[token] : humanizeToken(token);
111
+ }
112
+ //# sourceMappingURL=goal-words.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goal-words.js","sourceRoot":"","sources":["../../src/lib/goal-words.ts"],"names":[],"mappings":"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"}
@@ -60,23 +60,44 @@ export declare class HubNetworkError extends Error {
60
60
  });
61
61
  }
62
62
  /**
63
- * Append a one-line "need help?" footer with a clickable Discord invite. Prints
64
- * to stderr (same stream as the error it follows, so `2>/dev/null` hides both)
65
- * and only once per process. The invite is an OSC-8 hyperlink on an interactive
66
- * terminal, a plain URL when the stream is redirected/piped.
63
+ * Where to send users who hit a wall. The support door is the ONE customer
64
+ * support entry point shared by every Synap surface (`from=cli` tells support
65
+ * which surface the person came from); Discord stays as the community option.
67
66
  */
68
- export declare function discordHelpLine(): void;
67
+ export declare const SUPPORT_URL = "https://synap.live/support?from=cli";
68
+ /**
69
+ * Append a one-line "need help?" footer naming the support door (open a ticket)
70
+ * and the Discord invite. Prints to stderr (same stream as the error it
71
+ * follows, so `2>/dev/null` hides both) and only once per process. Each URL is
72
+ * an OSC-8 hyperlink on an interactive terminal, a plain URL when the stream is
73
+ * redirected/piped.
74
+ */
75
+ export declare function helpFooterLine(): void;
69
76
  /**
70
77
  * Print a failed Hub call: its humanized message plus a hint for the classes
71
78
  * where the cause is not obvious from the message alone. Lives here rather than
72
79
  * in utils/logger so the logger stays domain-agnostic and the taxonomy and its
73
- * presentation sit in one file. Always closes with the one-time Discord footer.
80
+ * presentation sit in one file. Always closes with the one-time help footer.
74
81
  */
75
82
  export declare function renderHubError(err: unknown): void;
76
83
  export interface HubConfig {
77
84
  podUrl: string;
78
85
  apiKey: string;
86
+ /**
87
+ * The ACTING identity sent to the pod (body/query/path `userId`). Only an
88
+ * explicit caller id (SYNAP_USER_ID) or the sentinel "cli"; read it through
89
+ * `resolveUserId`, which asks the pod (/users/me) for "cli". Never seed it
90
+ * with a recorded agent id: the pod acts an agent key as the human it is
91
+ * linked to and 403s the agent's own id ("userId does not match the
92
+ * authenticated identity", 2026-10-23). The agent's id is `agentUserId`.
93
+ */
79
94
  userId: string;
95
+ /**
96
+ * The agent principal recorded with this key, when one was — WHO the key
97
+ * is, not who the pod acts as. For recognising the agent's own posts
98
+ * (`resolveAgentUserId`), never sent as the acting user.
99
+ */
100
+ agentUserId?: string;
80
101
  /** Active workspace — from `synap use`, pod default, or env var. Undefined if none configured. */
81
102
  workspaceId?: string;
82
103
  /** Active project — from `synap project use`, pod default, or env var. Peer of workspaceId; independent. */
@@ -116,7 +137,7 @@ export declare function attachActiveSessionId(sessionId: string): SessionAttachT
116
137
  * which reads as the command failing.
117
138
  */
118
139
  export declare function detachActiveSessionId(podUrl?: string): string | undefined;
119
- export declare function hubGet(path: string, params: Record<string, string | number | undefined>, cfg: HubConfig): Promise<unknown>;
140
+ export declare function hubGet(path: string, params: Record<string, string | number | undefined>, cfg: HubConfig, timeoutMs?: number): Promise<unknown>;
120
141
  export declare function hubPost(path: string, body: unknown, cfg: HubConfig, timeoutMs?: number): Promise<unknown>;
121
142
  /**
122
143
  * Multipart POST for Hub doors that accept file uploads (e.g. source-file).
@@ -3,10 +3,11 @@ import { join } from "node:path";
3
3
  import { getActivePodConfig, getActiveWorkspaceId, getActiveWorkspaceIdForPod, findPodNameByUrl, getActiveProjectBinding, getActiveProjectId, listPodProfiles, getPodOverride, getSurfaceAgentKey, } from "./pod.js";
4
4
  import { resolveAgentOverride } from "./agents-config.js";
5
5
  import { resolveActiveLensForPod, getClaudeSessionId, writeLens, clearLensField, } from "./session-lens.js";
6
- import { conversationHeaders } from "./conversation-headers.js";
6
+ import { clientHeader, conversationHeaders } from "./conversation-headers.js";
7
7
  import { effectiveDirectoryLens, resolveDirectoryLensForPod, writeDirectoryLens, clearDirectoryLensField, } from "./directory-lens.js";
8
8
  import { preferClaudeCodeSurfaceKey } from "./key-source.js";
9
9
  import { samePodOrigin } from "./project-ref.js";
10
+ import { PodMovedError, podFetch } from "./pod-moved.js";
10
11
  import { HubRestClient } from "@synap-core/hub-rest-client";
11
12
  import { log } from "../utils/logger.js";
12
13
  import chalk from "chalk";
@@ -277,44 +278,48 @@ function hubError(method, path, status, rawBody, suffix, ctx) {
277
278
  workspaceId: ctx?.workspaceId,
278
279
  });
279
280
  }
280
- /** Where to send users who hit a wall. */
281
+ /**
282
+ * Where to send users who hit a wall. The support door is the ONE customer
283
+ * support entry point shared by every Synap surface (`from=cli` tells support
284
+ * which surface the person came from); Discord stays as the community option.
285
+ */
286
+ export const SUPPORT_URL = "https://synap.live/support?from=cli";
281
287
  const DISCORD_INVITE_URL = "https://discord.gg/xhRdQ7hG5h";
282
288
  /** Wrap a URL in an OSC-8 terminal hyperlink so `label` is clickable. */
283
289
  function osc8Link(url, label) {
284
290
  return `\x1b]8;;${url}\x1b\\${label}\x1b]8;;\x1b\\`;
285
291
  }
286
292
  /**
287
- * Once-per-process guard for the Discord help footer. A single CLI invocation
293
+ * Once-per-process guard for the help footer. A single CLI invocation
288
294
  * runs one command; printing the footer at most once keeps it a footer, not a
289
295
  * per-error/per-line spam.
290
296
  */
291
- let _discordHelpShown = false;
297
+ let _helpShown = false;
292
298
  /**
293
- * Append a one-line "need help?" footer with a clickable Discord invite. Prints
294
- * to stderr (same stream as the error it follows, so `2>/dev/null` hides both)
295
- * and only once per process. The invite is an OSC-8 hyperlink on an interactive
296
- * terminal, a plain URL when the stream is redirected/piped.
299
+ * Append a one-line "need help?" footer naming the support door (open a ticket)
300
+ * and the Discord invite. Prints to stderr (same stream as the error it
301
+ * follows, so `2>/dev/null` hides both) and only once per process. Each URL is
302
+ * an OSC-8 hyperlink on an interactive terminal, a plain URL when the stream is
303
+ * redirected/piped.
297
304
  */
298
- export function discordHelpLine() {
299
- if (_discordHelpShown)
305
+ export function helpFooterLine() {
306
+ if (_helpShown)
300
307
  return;
301
- _discordHelpShown = true;
302
- const link = process.stderr.isTTY
303
- ? osc8Link(DISCORD_INVITE_URL, DISCORD_INVITE_URL)
304
- : DISCORD_INVITE_URL;
305
- console.error(chalk.dim(` Need help? Join our Discord: ${link}`));
308
+ _helpShown = true;
309
+ const link = (url) => (process.stderr.isTTY ? osc8Link(url, url) : url);
310
+ console.error(chalk.dim(` Need help? Open a ticket: ${link(SUPPORT_URL)} (or join our Discord: ${link(DISCORD_INVITE_URL)})`));
306
311
  }
307
312
  /**
308
313
  * Print a failed Hub call: its humanized message plus a hint for the classes
309
314
  * where the cause is not obvious from the message alone. Lives here rather than
310
315
  * in utils/logger so the logger stays domain-agnostic and the taxonomy and its
311
- * presentation sit in one file. Always closes with the one-time Discord footer.
316
+ * presentation sit in one file. Always closes with the one-time help footer.
312
317
  */
313
318
  export function renderHubError(err) {
314
319
  renderHubErrorBody(err);
315
320
  // ONE central place every failed Hub command flows through — append the help
316
321
  // footer here so it prints once per failed command (guarded), not per line.
317
- discordHelpLine();
322
+ helpFooterLine();
318
323
  }
319
324
  function renderHubErrorBody(err) {
320
325
  // Hints use log.hint (stderr), not log.dim (stdout): a hint belongs to the
@@ -325,6 +330,11 @@ function renderHubErrorBody(err) {
325
330
  log.hint(err.hint);
326
331
  return;
327
332
  }
333
+ if (err instanceof PodMovedError) {
334
+ // One line: what happened and what to run.
335
+ log.error(err.message);
336
+ return;
337
+ }
328
338
  if (!(err instanceof HubError)) {
329
339
  log.error(err instanceof Error ? err.message : String(err));
330
340
  return;
@@ -475,8 +485,9 @@ export async function resolveHubConfig(opts) {
475
485
  return {
476
486
  podUrl: podOverride.podUrl,
477
487
  apiKey: podOverride.hubApiKey,
478
- // A profile's agentUserId may be empty; "cli" makes resolveUserId fetch /users/me.
479
- userId: podOverride.agentUserId || "cli",
488
+ // "cli": resolveUserId asks the pod who this key acts as (see HubConfig.userId).
489
+ userId: "cli",
490
+ agentUserId: podOverride.agentUserId || undefined,
480
491
  workspaceId: safeWs,
481
492
  projectId: resolveProjectIdForPod(podOverride.podUrl, {}),
482
493
  };
@@ -499,10 +510,13 @@ export async function resolveHubConfig(opts) {
499
510
  return {
500
511
  podUrl,
501
512
  apiKey: agentOverride.apiKey,
502
- // The AGENT's own id, recorded with its key. Never the pod profile name
503
- // (it was, so `synap worker` never recognised its own room posts and
504
- // resumed itself on them). Unrecorded → "cli": resolveUserId asks the pod.
505
- userId: agentOverride.agentUserId || "cli",
513
+ // The acting user is asked of the pod ("cli" → /users/me): an agent key
514
+ // acts as its linked human, and the pod 403s the agent's own id.
515
+ userId: "cli",
516
+ // The AGENT's own id, recorded with its key — for `synap worker` to
517
+ // recognise its own room posts (resolveAgentUserId). Never the pod
518
+ // profile name (it once was, and the worker resumed itself on them).
519
+ agentUserId: agentOverride.agentUserId || undefined,
506
520
  scopes: envScopes ? envScopes.split(",") : undefined,
507
521
  };
508
522
  }
@@ -513,7 +527,13 @@ export async function resolveHubConfig(opts) {
513
527
  const envScopes = process.env.SYNAP_KEY_SCOPES;
514
528
  const envWorkspace = process.env.SYNAP_WORKSPACE_ID;
515
529
  const envProject = process.env.SYNAP_PROJECT_ID;
516
- if (envPod && envKey && envUser) {
530
+ // Pod + key are enough. SYNAP_USER_ID used to be required too, so a shell
531
+ // with just the pair (`SYNAP_POD_URL=… SYNAP_HUB_API_KEY=… synap app
532
+ // connect`, the documented way to try another key) fell through to "No pod
533
+ // configured" (2026-10-20). Unset, userId is "cli" and resolveUserId asks
534
+ // the pod (/users/me) — the same fallback --pod and SYNAP_AGENT use. The
535
+ // claude-code surface-key preference below still applies.
536
+ if (envPod && envKey) {
517
537
  // Surface-bound resolution guard: if this pod has a `claude-code` surface
518
538
  // key pinned to it (via `synap connect --target=claude-code`) and the
519
539
  // ambient SYNAP_HUB_API_KEY doesn't match it, the session inherited a
@@ -534,6 +554,7 @@ export async function resolveHubConfig(opts) {
534
554
  }
535
555
  const apiKey = preferred.apiKey;
536
556
  const userId = preferred.userId;
557
+ const agentUserId = preferred.agentUserId;
537
558
  // Per-Claude-session lens wins over the global env var, so concurrent
538
559
  // sessions can each be scoped to a different workspace — but ONLY when the
539
560
  // lens belongs to THIS pod. A lens stamped with another pod is ignored so
@@ -550,6 +571,7 @@ export async function resolveHubConfig(opts) {
550
571
  podUrl: envPod,
551
572
  apiKey,
552
573
  userId,
574
+ agentUserId,
553
575
  // The global-config fallback is resolved AGAINST THIS POD (envPod) —
554
576
  // never send a workspace that belongs to a different pod.
555
577
  workspaceId: lens?.workspaceId ||
@@ -580,7 +602,10 @@ export async function resolveHubConfig(opts) {
580
602
  return {
581
603
  podUrl: config.podUrl,
582
604
  apiKey: config.hubApiKey,
583
- userId: config.agentUserId,
605
+ // "cli": resolveUserId asks the pod. A profile's agentUserId is the key's
606
+ // principal (a computer or agent key), not who the pod acts as.
607
+ userId: "cli",
608
+ agentUserId: config.agentUserId || undefined,
584
609
  // session lens › directory lens › global config. (No env rung here: this
585
610
  // branch is only reached when SYNAP_POD_URL is unset.)
586
611
  workspaceId: activeLens?.workspaceId || activeDirLens?.workspaceId || getActiveWorkspaceId(),
@@ -696,13 +721,18 @@ export function detachActiveSessionId(podUrl) {
696
721
  return previous;
697
722
  }
698
723
  /**
699
- * Per-request headers: the focus session this terminal is attached to, and the
724
+ * Per-request headers: which client calls (`synap-cli/<version>`), the focus
725
+ * session this terminal is attached to, and the
700
726
  * Claude Code conversation the call comes from (`conversation-headers.ts`) — so
701
727
  * the work it lands can be traced back to, and reopened in, that conversation.
702
728
  */
703
729
  function sessionHeaders(cfg) {
704
730
  const id = resolveActiveSessionId(cfg?.podUrl);
705
- return { ...conversationHeaders(getClaudeSessionId()), ...(id ? { "X-Session-Id": id } : {}) };
731
+ return {
732
+ ...clientHeader("synap-cli"),
733
+ ...conversationHeaders(getClaudeSessionId()),
734
+ ...(id ? { "X-Session-Id": id } : {}),
735
+ };
706
736
  }
707
737
  /** Max 429 retries for bulk import (each waits up to the rate-limit window). */
708
738
  const HUB_429_MAX_RETRIES = 12;
@@ -786,9 +816,11 @@ async function hubFetchWithRetry(url, init, label, timeoutMs) {
786
816
  await acquireHubSlot();
787
817
  let res;
788
818
  try {
789
- res = await fetch(url, init);
819
+ res = await podFetch(url, init);
790
820
  }
791
821
  catch (err) {
822
+ if (err instanceof PodMovedError)
823
+ throw err;
792
824
  throw networkError(err, url, labelMethod, labelPath, timeoutMs);
793
825
  }
794
826
  if (res.status !== 429)
@@ -804,7 +836,10 @@ async function hubFetchWithRetry(url, init, label, timeoutMs) {
804
836
  await sleep(wait);
805
837
  }
806
838
  }
807
- export async function hubGet(path, params, cfg) {
839
+ export async function hubGet(path, params, cfg,
840
+ // Default 15s suits reads. A long-poll door (`/answers/wait`) holds the
841
+ // request open for its own `timeoutSeconds`; its caller passes more.
842
+ timeoutMs = 15_000) {
808
843
  const url = new URL(`${cfg.podUrl}/api/hub${path}`);
809
844
  for (const [k, v] of Object.entries(params)) {
810
845
  if (v !== undefined)
@@ -812,8 +847,8 @@ export async function hubGet(path, params, cfg) {
812
847
  }
813
848
  const res = await hubFetchWithRetry(url.toString(), {
814
849
  headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
815
- signal: AbortSignal.timeout(15_000),
816
- }, `GET ${path}`, 15_000);
850
+ signal: AbortSignal.timeout(timeoutMs),
851
+ }, `GET ${path}`, timeoutMs);
817
852
  if (!res.ok) {
818
853
  const body = await res.text().catch(() => "");
819
854
  throw hubError("GET", path, res.status, body, undefined, cfg);
@@ -887,7 +922,7 @@ export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs
887
922
  const url = `${cfg.podUrl}/api/hub${path}`;
888
923
  let res;
889
924
  try {
890
- res = await fetch(url, {
925
+ res = await podFetch(url, {
891
926
  method: "POST",
892
927
  headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
893
928
  body: form,
@@ -895,6 +930,8 @@ export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs
895
930
  });
896
931
  }
897
932
  catch (err) {
933
+ if (err instanceof PodMovedError)
934
+ throw err;
898
935
  throw networkError(err, url, "POST multipart", path, timeoutMs);
899
936
  }
900
937
  if (res.status !== 429) {
@@ -919,7 +956,7 @@ export async function hubPatch(path, body, cfg) {
919
956
  const url = `${cfg.podUrl}/api/hub${path}`;
920
957
  let res;
921
958
  try {
922
- res = await fetch(url, {
959
+ res = await podFetch(url, {
923
960
  method: "PATCH",
924
961
  headers: { Authorization: `Bearer ${cfg.apiKey}`, "Content-Type": "application/json", ...sessionHeaders(cfg) },
925
962
  body: JSON.stringify(body),
@@ -927,6 +964,8 @@ export async function hubPatch(path, body, cfg) {
927
964
  });
928
965
  }
929
966
  catch (err) {
967
+ if (err instanceof PodMovedError)
968
+ throw err;
930
969
  throw networkError(err, url, "PATCH", path, 15_000);
931
970
  }
932
971
  if (!res.ok) {
@@ -942,13 +981,15 @@ export async function hubDelete(path, cfg) {
942
981
  const url = `${cfg.podUrl}/api/hub${path}`;
943
982
  let res;
944
983
  try {
945
- res = await fetch(url, {
984
+ res = await podFetch(url, {
946
985
  method: "DELETE",
947
986
  headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
948
987
  signal: AbortSignal.timeout(15_000),
949
988
  });
950
989
  }
951
990
  catch (err) {
991
+ if (err instanceof PodMovedError)
992
+ throw err;
952
993
  throw networkError(err, url, "DELETE", path, 15_000);
953
994
  }
954
995
  if (!res.ok) {