@synap-core/cli 1.13.0 → 1.13.1-next.20261102.4b45534

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 (221) hide show
  1. package/README.md +13 -3
  2. package/dist/build-info.json +2 -3
  3. package/dist/commands/agents.js +12 -2
  4. package/dist/commands/agents.js.map +1 -1
  5. package/dist/commands/capability.d.ts +86 -0
  6. package/dist/commands/capability.js +117 -30
  7. package/dist/commands/capability.js.map +1 -1
  8. package/dist/commands/data-extra.d.ts +13 -0
  9. package/dist/commands/data-extra.js +23 -2
  10. package/dist/commands/data-extra.js.map +1 -1
  11. package/dist/commands/doctor.d.ts +18 -0
  12. package/dist/commands/doctor.js +28 -1
  13. package/dist/commands/doctor.js.map +1 -1
  14. package/dist/commands/init.js +4 -2
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/keys-request.d.ts +48 -0
  17. package/dist/commands/keys-request.js +236 -0
  18. package/dist/commands/keys-request.js.map +1 -0
  19. package/dist/commands/keys-request.test.d.ts +1 -0
  20. package/dist/commands/keys-request.test.js +176 -0
  21. package/dist/commands/keys-request.test.js.map +1 -0
  22. package/dist/commands/knowledge.d.ts +6 -0
  23. package/dist/commands/knowledge.js +9 -1
  24. package/dist/commands/knowledge.js.map +1 -1
  25. package/dist/commands/market.d.ts +74 -6
  26. package/dist/commands/market.js +162 -53
  27. package/dist/commands/market.js.map +1 -1
  28. package/dist/commands/mcp.d.ts +1 -1
  29. package/dist/commands/mcp.js +14 -9
  30. package/dist/commands/mcp.js.map +1 -1
  31. package/dist/commands/pods.js +6 -2
  32. package/dist/commands/pods.js.map +1 -1
  33. package/dist/commands/proposals-actions.js +7 -1
  34. package/dist/commands/proposals-actions.js.map +1 -1
  35. package/dist/commands/providers.d.ts +22 -6
  36. package/dist/commands/providers.js +39 -32
  37. package/dist/commands/providers.js.map +1 -1
  38. package/dist/commands/raycast.d.ts +15 -0
  39. package/dist/commands/raycast.js +36 -10
  40. package/dist/commands/raycast.js.map +1 -1
  41. package/dist/commands/status.js +12 -6
  42. package/dist/commands/status.js.map +1 -1
  43. package/dist/commands/statusline.backoff.test.js +13 -3
  44. package/dist/commands/statusline.backoff.test.js.map +1 -1
  45. package/dist/commands/statusline.d.ts +104 -9
  46. package/dist/commands/statusline.js +392 -148
  47. package/dist/commands/statusline.js.map +1 -1
  48. package/dist/commands/statusline.line2.test.d.ts +15 -0
  49. package/dist/commands/statusline.line2.test.js +226 -0
  50. package/dist/commands/statusline.line2.test.js.map +1 -0
  51. package/dist/commands/statusline.needs-you.test.d.ts +1 -0
  52. package/dist/commands/statusline.needs-you.test.js +17 -0
  53. package/dist/commands/statusline.needs-you.test.js.map +1 -0
  54. package/dist/commands/statusline.now.test.d.ts +10 -0
  55. package/dist/commands/statusline.now.test.js +250 -0
  56. package/dist/commands/statusline.now.test.js.map +1 -0
  57. package/dist/commands/tracks.d.ts +1 -0
  58. package/dist/commands/tracks.js +4 -0
  59. package/dist/commands/tracks.js.map +1 -1
  60. package/dist/commands/upload.js +12 -0
  61. package/dist/commands/upload.js.map +1 -1
  62. package/dist/commands/wait.d.ts +124 -0
  63. package/dist/commands/wait.js +384 -0
  64. package/dist/commands/wait.js.map +1 -0
  65. package/dist/commands/wait.test.d.ts +8 -0
  66. package/dist/commands/wait.test.js +308 -0
  67. package/dist/commands/wait.test.js.map +1 -0
  68. package/dist/commands/worker-github-app.d.ts +63 -0
  69. package/dist/commands/worker-github-app.js +161 -0
  70. package/dist/commands/worker-github-app.js.map +1 -0
  71. package/dist/commands/worker-github-app.test.d.ts +1 -0
  72. package/dist/commands/worker-github-app.test.js +102 -0
  73. package/dist/commands/worker-github-app.test.js.map +1 -0
  74. package/dist/commands/worker-github-broker.d.ts +39 -0
  75. package/dist/commands/worker-github-broker.js +106 -0
  76. package/dist/commands/worker-github-broker.js.map +1 -0
  77. package/dist/commands/worker-github-broker.test.d.ts +1 -0
  78. package/dist/commands/worker-github-broker.test.js +90 -0
  79. package/dist/commands/worker-github-broker.test.js.map +1 -0
  80. package/dist/commands/worker-runs.test.d.ts +1 -0
  81. package/dist/commands/worker-runs.test.js +150 -0
  82. package/dist/commands/worker-runs.test.js.map +1 -0
  83. package/dist/commands/worker-worktrees.d.ts +34 -0
  84. package/dist/commands/worker-worktrees.js +87 -0
  85. package/dist/commands/worker-worktrees.js.map +1 -0
  86. package/dist/commands/worker.d.ts +192 -5
  87. package/dist/commands/worker.js +467 -45
  88. package/dist/commands/worker.js.map +1 -1
  89. package/dist/commands/worker.test.js +269 -3
  90. package/dist/commands/worker.test.js.map +1 -1
  91. package/dist/index.js +59 -4
  92. package/dist/index.js.map +1 -1
  93. package/dist/lib/agents-config.d.ts +9 -0
  94. package/dist/lib/agents-config.js +20 -0
  95. package/dist/lib/agents-config.js.map +1 -1
  96. package/dist/lib/atomic-file.d.ts +20 -1
  97. package/dist/lib/atomic-file.js +100 -2
  98. package/dist/lib/atomic-file.js.map +1 -1
  99. package/dist/lib/auth.d.ts +25 -0
  100. package/dist/lib/auth.js +49 -9
  101. package/dist/lib/auth.js.map +1 -1
  102. package/dist/lib/box/job-server.js +4 -0
  103. package/dist/lib/box/job-server.js.map +1 -1
  104. package/dist/lib/bridge-invocation.test.d.ts +1 -0
  105. package/dist/lib/bridge-invocation.test.js +75 -0
  106. package/dist/lib/bridge-invocation.test.js.map +1 -0
  107. package/dist/lib/bridge-status.d.ts +144 -0
  108. package/dist/lib/bridge-status.js +227 -0
  109. package/dist/lib/bridge-status.js.map +1 -0
  110. package/dist/lib/bridge-status.test.d.ts +6 -0
  111. package/dist/lib/bridge-status.test.js +197 -0
  112. package/dist/lib/bridge-status.test.js.map +1 -0
  113. package/dist/lib/client-header.test.js +19 -1
  114. package/dist/lib/client-header.test.js.map +1 -1
  115. package/dist/lib/conversation-headers.d.ts +67 -2
  116. package/dist/lib/conversation-headers.js +122 -6
  117. package/dist/lib/conversation-headers.js.map +1 -1
  118. package/dist/lib/conversation-headers.test.d.ts +1 -0
  119. package/dist/lib/conversation-headers.test.js +78 -0
  120. package/dist/lib/conversation-headers.test.js.map +1 -0
  121. package/dist/lib/credential-class.d.ts +12 -1
  122. package/dist/lib/credential-class.js +20 -0
  123. package/dist/lib/credential-class.js.map +1 -1
  124. package/dist/lib/device-connect.d.ts +45 -0
  125. package/dist/lib/device-connect.js +60 -0
  126. package/dist/lib/device-connect.js.map +1 -1
  127. package/dist/lib/goal-words.js +6 -3
  128. package/dist/lib/goal-words.js.map +1 -1
  129. package/dist/lib/hub-client.d.ts +59 -1
  130. package/dist/lib/hub-client.js +130 -36
  131. package/dist/lib/hub-client.js.map +1 -1
  132. package/dist/lib/key-locations.d.ts +3 -3
  133. package/dist/lib/key-locations.js +3 -39
  134. package/dist/lib/key-locations.js.map +1 -1
  135. package/dist/lib/machine-instance.d.ts +7 -5
  136. package/dist/lib/machine-instance.js +7 -5
  137. package/dist/lib/machine-instance.js.map +1 -1
  138. package/dist/lib/mcp-bridge.d.ts +43 -0
  139. package/dist/lib/mcp-bridge.js +199 -11
  140. package/dist/lib/mcp-bridge.js.map +1 -1
  141. package/dist/lib/mcp-bridge.test.js +194 -2
  142. package/dist/lib/mcp-bridge.test.js.map +1 -1
  143. package/dist/lib/mcp-call.d.ts +5 -1
  144. package/dist/lib/mcp-call.js +7 -6
  145. package/dist/lib/mcp-call.js.map +1 -1
  146. package/dist/lib/mcp-connector-url.test.d.ts +1 -0
  147. package/dist/lib/mcp-connector-url.test.js +13 -0
  148. package/dist/lib/mcp-connector-url.test.js.map +1 -0
  149. package/dist/lib/mcp-snippets.d.ts +16 -0
  150. package/dist/lib/mcp-snippets.js +20 -2
  151. package/dist/lib/mcp-snippets.js.map +1 -1
  152. package/dist/lib/now-line.d.ts +66 -0
  153. package/dist/lib/now-line.js +193 -0
  154. package/dist/lib/now-line.js.map +1 -0
  155. package/dist/lib/now-line.test.d.ts +5 -0
  156. package/dist/lib/now-line.test.js +177 -0
  157. package/dist/lib/now-line.test.js.map +1 -0
  158. package/dist/lib/package-updates.d.ts +66 -0
  159. package/dist/lib/package-updates.js +76 -0
  160. package/dist/lib/package-updates.js.map +1 -0
  161. package/dist/lib/pod.d.ts +0 -4
  162. package/dist/lib/pod.js +0 -7
  163. package/dist/lib/pod.js.map +1 -1
  164. package/dist/lib/register-claude-code-mcp.test.js +19 -0
  165. package/dist/lib/register-claude-code-mcp.test.js.map +1 -1
  166. package/dist/lib/safe-open.d.ts +5 -3
  167. package/dist/lib/safe-open.js +9 -8
  168. package/dist/lib/safe-open.js.map +1 -1
  169. package/dist/lib/self-host.d.ts +17 -0
  170. package/dist/lib/self-host.js +24 -0
  171. package/dist/lib/self-host.js.map +1 -0
  172. package/dist/lib/targets.d.ts +31 -0
  173. package/dist/lib/targets.js +100 -104
  174. package/dist/lib/targets.js.map +1 -1
  175. package/dist/lib/term.d.ts +24 -0
  176. package/dist/lib/term.js +52 -0
  177. package/dist/lib/term.js.map +1 -0
  178. package/dist/lib/term.test.d.ts +1 -0
  179. package/dist/lib/term.test.js +29 -0
  180. package/dist/lib/term.test.js.map +1 -0
  181. package/dist/lib/transcript-index.d.ts +94 -0
  182. package/dist/lib/transcript-index.js +321 -0
  183. package/dist/lib/transcript-index.js.map +1 -0
  184. package/dist/lib/transcript-index.test.d.ts +10 -0
  185. package/dist/lib/transcript-index.test.js +200 -0
  186. package/dist/lib/transcript-index.test.js.map +1 -0
  187. package/dist/lib/wait-help.d.ts +11 -0
  188. package/dist/lib/wait-help.js +25 -0
  189. package/dist/lib/wait-help.js.map +1 -0
  190. package/dist/lib/wait-state.d.ts +57 -0
  191. package/dist/lib/wait-state.js +114 -0
  192. package/dist/lib/wait-state.js.map +1 -0
  193. package/package.json +2 -2
  194. package/skills/synap/SKILL.md +71 -38
  195. package/skills/synap/code-skills.md +15 -1
  196. package/skills/synap/concepts.md +36 -26
  197. package/skills/synap/document-embeds.md +15 -1
  198. package/skills/synap/focus-sessions.md +4 -4
  199. package/skills/synap/from-intent.md +2 -2
  200. package/skills/synap/governance-rules.md +2 -0
  201. package/skills/synap/reflexes.md +4 -4
  202. package/skills/synap/work-model.md +1 -1
  203. package/skills/synap/writing-titles.md +7 -0
  204. package/skills/synap-app/SKILL.md +2 -2
  205. package/skills/synap-app/overview.md +2 -2
  206. package/skills/synap-market/SKILL.md +1 -1
  207. package/skills/synap-market/governance-and-catalog.md +1 -1
  208. package/skills/synap-schema/SKILL.md +4 -1
  209. package/skills/synap-schema/intent-vocabulary-ssot.md +4 -1
  210. package/dist/commands/template.d.ts +0 -42
  211. package/dist/commands/template.js +0 -191
  212. package/dist/commands/template.js.map +0 -1
  213. package/dist/lib/browser-auth.d.ts +0 -35
  214. package/dist/lib/browser-auth.js +0 -170
  215. package/dist/lib/browser-auth.js.map +0 -1
  216. package/dist/lib/openclaw.d.ts +0 -32
  217. package/dist/lib/openclaw.js +0 -165
  218. package/dist/lib/openclaw.js.map +0 -1
  219. package/dist/lib/seed.d.ts +0 -13
  220. package/dist/lib/seed.js +0 -135
  221. package/dist/lib/seed.js.map +0 -1
@@ -26,18 +26,21 @@ import chalk from "chalk";
26
26
  import { assembleCatalog, computeTemplateUpdates, isHashVersion, listPublicTemplates, listWorkspaceTemplates, getWorkspaceTemplate, mergeCatalog, toPackageDefinition, layerTemplateGraph, bundledEdgesOf, groupByDomain, } from "@synap-core/workspace-templates";
27
27
  import { log } from "../utils/logger.js";
28
28
  import { resolveHubConfig, hubPost, renderHubError, HubError } from "../lib/hub-client.js";
29
+ import { classifyActiveCredential } from "../lib/credential-class.js";
29
30
  import { fetchProjects } from "../lib/project.js";
30
31
  import { writeGovernance } from "../lib/capture-lane.js";
31
32
  import { renderNextSteps, FLOW } from "../lib/next-steps.js";
32
33
  import { getStoredToken, isTokenLocallyExpired } from "../lib/auth.js";
33
34
  import prompts from "prompts";
34
35
  import { fetchPublicBrowseRows, fetchMyRows, fetchAvailableSlugs, fetchRetiredWorkspaceSlugs, fetchPackageDefinition, fetchPackageRecord, hydrateMineCategories, } from "../lib/cp-packages.js";
35
- import { fetchInstalledSlugs, fetchInstalledTemplates, fetchInstalledInventory, fetchInstalledInventoryStrict, installedRowsToTemplates, fetchWorkspaceAttachments, fetchWorkspaceAttachmentsStrict, verifyStampLanded, INSTALLED_KINDS } from "../lib/installed.js";
36
+ import { fetchInstalledSlugs, fetchInstalledTemplates, fetchInstalledInventory, installedRowsToTemplates, fetchWorkspaceAttachments, fetchWorkspaceAttachmentsStrict, verifyStampLanded, INSTALLED_KINDS } from "../lib/installed.js";
36
37
  import { isSuite } from "../lib/suite.js";
37
38
  import { TEMPLATE_STEP_LABELS, classifyTemplateStep, proposalsDoor, stepFromApplyState, templateStepGlyph, templateStepPhrase, } from "../lib/template-steps.js";
38
39
  import { capabilityAdd } from "./capability.js";
39
40
  import { bundledTemplatesVersion } from "../lib/bundle-version.js";
40
41
  import { HubNetworkError } from "../lib/hub-client.js";
42
+ import { checksFromStandings, fetchPackageStandings, StandingsUnavailableError, } from "../lib/package-updates.js";
43
+ import { podAdminOrigin } from "../lib/safe-open.js";
41
44
  import { parseDataChanges, dataChangesFromRefusal, consentNeedsPerson, dataChangeRefusalMessage, printDataChanges, parseAcceptList, decideConsent, consentRerunCommand, consentRefusalLines, lifecycleNotice, overlayNotice, overlayNeedsAttention, } from "../lib/template-lifecycle.js";
42
45
  // Same longer budget launch uses — an apply writes profiles/views/entities.
43
46
  const APPLY_TIMEOUT_MS = 120_000;
@@ -167,8 +170,8 @@ const KIND_HEADINGS = {
167
170
  // (`GET /installed`) calls the same thing `automation`. Both tokens reach
168
171
  // `kindHeading()` — `market --list` speaks CP, `market installed` speaks pod —
169
172
  // so both are mapped, to the ONE plural the registry says.
170
- workflow: "Rules",
171
- automation: "Rules",
173
+ workflow: "Automations",
174
+ automation: "Automations",
172
175
  view: "Views",
173
176
  cell: "Cards",
174
177
  workspace: "Spaces",
@@ -1260,18 +1263,48 @@ function stampVersion(pkg, slug, version) {
1260
1263
  const meta = pkg._meta ?? {};
1261
1264
  return { ...pkg, _meta: { ...meta, slug, ...(version ? { version } : {}) } };
1262
1265
  }
1263
- export function updateDoorFor(kind) {
1266
+ /**
1267
+ * The slugs with an update that `synap market update <slug>` can apply (a
1268
+ * kind with a CLI updater, for this key). Next-step suggestions use it, so
1269
+ * they never offer a command that would only refuse.
1270
+ */
1271
+ export function cliUpdatableSlugs(checks, opts = {}) {
1272
+ return checks
1273
+ .filter((c) => {
1274
+ if (!c.updateAvailable)
1275
+ return false;
1276
+ const door = updateDoorFor(c.kind, opts.agentKey);
1277
+ return door !== null && door !== "pod-admin";
1278
+ })
1279
+ .map((c) => c.slug);
1280
+ }
1281
+ /**
1282
+ * `agentKey`: the pod refuses a pod-wide config reconcile to any agent
1283
+ * credential ("An agent credential cannot trigger a pod-wide config
1284
+ * reconcile", 403), so for such a key a view / skill / automation update is
1285
+ * the person's act, like a capability: its door is `"pod-admin"`.
1286
+ */
1287
+ export function updateDoorFor(kind, agentKey = false) {
1264
1288
  switch (kind ?? "workspace") {
1265
1289
  case "workspace":
1266
1290
  return "packages-apply";
1267
1291
  case "view":
1268
1292
  case "skill":
1269
1293
  case "automation":
1270
- return "configs-reconcile";
1294
+ return agentKey ? "pod-admin" : "configs-reconcile";
1271
1295
  default:
1272
1296
  return null;
1273
1297
  }
1274
1298
  }
1299
+ /** The status of an update left for the person to apply in pod admin (not a failure). */
1300
+ export const APPLY_IN_POD_ADMIN = "apply-in-pod-admin";
1301
+ /** The one row for a package this key cannot update: the person applies it in pod admin. */
1302
+ function applyInPodAdminResult(c, podUrl, json) {
1303
+ if (!json) {
1304
+ log.warn(`${c.slug} is a ${c.kind ?? "workspace"} — to apply in pod admin. ${podUrl ? marketUpdatesDoor(podUrl) : "Apply it yourself from pod admin → Marketplace → Updates."} Nothing was written.`);
1305
+ }
1306
+ return { slug: c.slug, outcome: APPLY_IN_POD_ADMIN, status: APPLY_IN_POD_ADMIN, warnings: [] };
1307
+ }
1275
1308
  /**
1276
1309
  * Per-installed-slug drift check. SERVER-FIRST: when the pod runs the
1277
1310
  * server-side TemplateHealth code (Hub `/workspaces` returns `drifted`/
@@ -1341,7 +1374,7 @@ export function dedupeBySlug(checks) {
1341
1374
  * pod's idempotent-create path, which reconciles when the pod build supports
1342
1375
  * it (see the `applyOnePackage` caveat below for what "created" means here).
1343
1376
  */
1344
- async function applyOnePackage(entry, cat, cfg, opts, applyTimeoutMs = APPLY_TIMEOUT_MS, extra = {}) {
1377
+ export async function applyOnePackage(entry, cat, cfg, opts, applyTimeoutMs = APPLY_TIMEOUT_MS, extra = {}) {
1345
1378
  if (cat.lockedSlugs.has(entry.slug)) {
1346
1379
  const tier = cat.tierBySlug.get(entry.slug)?.requiredTier;
1347
1380
  if (!opts.json)
@@ -1366,14 +1399,28 @@ async function applyOnePackage(entry, cat, cfg, opts, applyTimeoutMs = APPLY_TIM
1366
1399
  try {
1367
1400
  const res = (await hubPost("/packages/apply", pkg, cfg, applyTimeoutMs));
1368
1401
  s?.stop();
1402
+ // A key that may not apply a workspace package on its own (a computer
1403
+ // key's grant leaves out `workspace.update`) files the update for the
1404
+ // person's approval: 202 `{status:"proposed", proposalId, reviewUrl}`.
1405
+ // Waiting, never failed — it applies when the person approves it.
1369
1406
  if (writeGovernance(res) === "proposed") {
1370
1407
  const proposalId = res.proposalId ? String(res.proposalId) : undefined;
1408
+ const reviewUrl = approvalLink(cfg.podUrl, proposalId, res.reviewUrl);
1371
1409
  if (!opts.json) {
1372
- log.info(`${entry.name} — update proposed (under review, not live yet).`);
1373
- if (proposalId)
1374
- log.hint(`Review and approve: synap open proposal ${proposalId}`);
1410
+ log.info(`${entry.name} — update filed for your approval (not applied yet).`);
1411
+ if (reviewUrl)
1412
+ log.hint(`Approve it: ${reviewUrl}`);
1413
+ else if (proposalId)
1414
+ log.hint(`Approve it: synap open proposal ${proposalId}`);
1375
1415
  }
1376
- return { slug: entry.slug, outcome: "proposed", status: "proposed", warnings: [] };
1416
+ return {
1417
+ slug: entry.slug,
1418
+ outcome: "proposed",
1419
+ status: "proposed",
1420
+ warnings: [],
1421
+ ...(proposalId ? { proposalId } : {}),
1422
+ ...(reviewUrl ? { reviewUrl } : {}),
1423
+ };
1377
1424
  }
1378
1425
  const ws = res.workspace;
1379
1426
  const dependencies = res.dependencies;
@@ -1431,7 +1478,7 @@ async function applyOnePackage(entry, cat, cfg, opts, applyTimeoutMs = APPLY_TIM
1431
1478
  if (refusal)
1432
1479
  log.warn(refusal);
1433
1480
  else
1434
- renderHubError(e);
1481
+ renderHubError(e, { personDoor: marketUpdatesDoor(cfg.podUrl) });
1435
1482
  }
1436
1483
  return {
1437
1484
  slug: entry.slug,
@@ -1442,6 +1489,29 @@ async function applyOnePackage(entry, cat, cfg, opts, applyTimeoutMs = APPLY_TIM
1442
1489
  };
1443
1490
  }
1444
1491
  }
1492
+ /**
1493
+ * Where the person approves a filed update: pod admin's proposal page, the
1494
+ * approval door (`/proposal/<id>`), when the pod-admin origin derives from the
1495
+ * pod's; else the link the pod sent (`reviewUrl`); else nothing.
1496
+ */
1497
+ export function approvalLink(podUrl, proposalId, reviewUrl) {
1498
+ const admin = podAdminOrigin(podUrl);
1499
+ if (admin && proposalId)
1500
+ return `${admin}/proposal/${encodeURIComponent(proposalId)}`;
1501
+ return typeof reviewUrl === "string" && reviewUrl.length > 0 ? reviewUrl : undefined;
1502
+ }
1503
+ /**
1504
+ * Where the person applies a package update a limited key may not apply
1505
+ * (a computer key's grant leaves out `workspace.update`, by design: applying a
1506
+ * workspace package is the person's act). Pod admin's Marketplace, Updates
1507
+ * tab, named by URL when the pod-admin origin derives from the pod's.
1508
+ */
1509
+ export function marketUpdatesDoor(podUrl) {
1510
+ const admin = podAdminOrigin(podUrl);
1511
+ return admin
1512
+ ? `Apply it yourself from pod admin → Marketplace → Updates: ${admin}/marketplace/updates`
1513
+ : "Apply it yourself from pod admin → Marketplace → Updates.";
1514
+ }
1445
1515
  /**
1446
1516
  * Ask the pod's write-free preview door (`POST /packages/preflight`, the same
1447
1517
  * body `/packages/apply` takes) which data changes an update would run.
@@ -1466,6 +1536,9 @@ async function reconcileOutcome(c, report, opts) {
1466
1536
  r = await report;
1467
1537
  }
1468
1538
  catch (e) {
1539
+ // The pod's own refusal of an agent credential is the person's door, not a failure.
1540
+ if (e instanceof HubError && e.status === 403 && `${e.message} ${e.rawBody}`.toLowerCase().includes("agent credential"))
1541
+ return applyInPodAdminResult(c, opts.podUrl, opts.json);
1469
1542
  if (!opts.json) {
1470
1543
  log.error(`${c.slug} update failed`);
1471
1544
  renderHubError(e);
@@ -1509,26 +1582,28 @@ async function reconcileOutcome(c, report, opts) {
1509
1582
  }
1510
1583
  export async function marketUpdate(slugsArg, opts) {
1511
1584
  const applyTimeoutMs = parseApplyTimeoutMs(opts.timeout);
1512
- // STRICT fetch: distinguish "pod unreachable" (transient — don't claim the
1513
- // pod is empty, that reads as data loss) from "genuinely nothing installed".
1514
1585
  const cat = await buildMarketCatalog();
1515
- let installedTemplates;
1516
- // Whether the pod answered from the ONE read door (`GET /installed`, all six
1517
- // kinds) or is old enough that only workspace packages were visible. An old
1518
- // pod is not an empty pod — say which, rather than letting a narrower list
1519
- // read as the whole truth.
1520
- let degraded = false;
1586
+ // THE POD'S ANSWER, never the CLI's: which installed package has an update
1587
+ // is derived once on the pod, for every kind, with one rule (Hub
1588
+ // `GET /package-updates` → `installed[]`), and pod admin's Updates tab reads
1589
+ // the same list. A pod that cannot answer is said out loud, never replaced
1590
+ // by a verdict computed here (that fork is how the CLI said "content
1591
+ // changed" while pod admin said nothing, 2 Nov 2026).
1592
+ let standings;
1521
1593
  try {
1522
- const inv = await fetchInstalledInventoryStrict();
1523
- installedTemplates = installedRowsToTemplates(inv.rows);
1524
- degraded = inv.degraded;
1594
+ standings = await fetchPackageStandings(await resolveHubConfig(opts));
1525
1595
  }
1526
1596
  catch (e) {
1527
1597
  // Unreachable ≠ success — a script checking `$?` (or `&&`-chaining) must see
1528
1598
  // failure, not exit 0, even though the JSON payload also carries the reason.
1529
1599
  process.exitCode = 1;
1600
+ const unavailable = e instanceof StandingsUnavailableError ? e.reason : null;
1530
1601
  if (opts.json) {
1531
- console.log(JSON.stringify({ checks: [], loggedIn: cat.loggedIn, error: "pod-unreachable" }, null, 2));
1602
+ console.log(JSON.stringify({ checks: [], loggedIn: cat.loggedIn, error: unavailable ?? "pod-unreachable" }, null, 2));
1603
+ return;
1604
+ }
1605
+ if (e instanceof StandingsUnavailableError) {
1606
+ log.error(e.message);
1532
1607
  return;
1533
1608
  }
1534
1609
  log.error("Couldn't reach your pod to read what's installed — not saying it's empty.");
@@ -1536,26 +1611,21 @@ export async function marketUpdate(slugsArg, opts) {
1536
1611
  log.hint("Check the pod is up / you're on the right --pod, then retry.");
1537
1612
  return;
1538
1613
  }
1539
- const degradedNote = degraded
1540
- ? "This pod does not serve GET /api/hub/installed — only workspace packages were checked. Capability / view / skill / automation / card installs were NOT (upgrade the pod to include them)."
1541
- : undefined;
1542
- if (installedTemplates.length === 0) {
1614
+ // Kept in the JSON for readers of the older shape: the pod's list covers
1615
+ // every kind, so it is never the workspace-only view of an old pod.
1616
+ const degraded = false;
1617
+ if (standings.length === 0) {
1543
1618
  if (opts.json) {
1544
- console.log(JSON.stringify({ checks: [], loggedIn: cat.loggedIn, degraded, ...(degradedNote ? { degradedNote } : {}) }, null, 2));
1619
+ console.log(JSON.stringify({ checks: [], loggedIn: cat.loggedIn, degraded }, null, 2));
1545
1620
  return;
1546
1621
  }
1547
1622
  log.warn("No installed packages found on this pod.");
1548
- if (degradedNote)
1549
- log.dim(degradedNote);
1550
1623
  log.hint("Install one: synap market install <slug>");
1551
1624
  return;
1552
1625
  }
1553
- // ONE ROW PER SLUG. `GET /installed` reports one row per installed OBJECT —
1554
- // a views pack that installed four views is four rows — and an update is
1555
- // applied per PACKAGE, not per object. Without this the preview listed
1556
- // `task-views-pack` four times and the "N up to date" tally counted it four
1557
- // times. `market installed` has always deduped the same way.
1558
- let checks = dedupeBySlug(computeUpdates(installedTemplates, cat));
1626
+ // One check per slug: the pod answers per package (slug + kind); a slug
1627
+ // installed as two kinds is updated per kind by its own door, but listed once.
1628
+ let checks = dedupeBySlug(checksFromStandings(standings));
1559
1629
  const entryBySlug = new Map(cat.entries.map((e) => [e.slug, e]));
1560
1630
  const explicitSlugs = !!slugsArg && slugsArg.length > 0;
1561
1631
  let unknownSlugs = [];
@@ -1612,6 +1682,7 @@ export async function marketUpdate(slugsArg, opts) {
1612
1682
  // Asked of the pod's write-free preview door BEFORE anything is shown, so the
1613
1683
  // person reads what will happen to their data before the template diff.
1614
1684
  let cfg;
1685
+ let agentKey = false;
1615
1686
  const previews = new Map();
1616
1687
  if (withUpdates.length > 0) {
1617
1688
  try {
@@ -1621,6 +1692,12 @@ export async function marketUpdate(slugsArg, opts) {
1621
1692
  renderHubError(e);
1622
1693
  process.exit(1);
1623
1694
  }
1695
+ // Which door applies a view / skill / automation depends on the key: the
1696
+ // pod refuses the pod-wide reconcile to any agent credential. Asked only
1697
+ // when such an update exists, through the one credential-class read.
1698
+ if (withUpdates.some((c) => updateDoorFor(c.kind) === "configs-reconcile")) {
1699
+ agentKey = (await classifyActiveCredential(cfg)) === "agent";
1700
+ }
1624
1701
  for (const c of withUpdates) {
1625
1702
  // The data-change preview is the workspace door's (`/packages/preflight`).
1626
1703
  if (updateDoorFor(c.kind) !== "packages-apply")
@@ -1663,6 +1740,8 @@ export async function marketUpdate(slugsArg, opts) {
1663
1740
  }
1664
1741
  log.heading(" Installed packages");
1665
1742
  log.blank();
1743
+ // The slug column fits the longest slug (capped), with at least 2 spaces after it.
1744
+ const slugW = Math.min(Math.max(28, ...checks.map((c) => c.slug.length + 2)), 46);
1666
1745
  for (const c of checks) {
1667
1746
  // ONE label door. This was a hand-rolled second copy that diverged from
1668
1747
  // `installedStatusLabel` in two ways that both LIED: it had no cache-cold
@@ -1673,14 +1752,12 @@ export async function marketUpdate(slugsArg, opts) {
1673
1752
  const label = retiredSlugs.has(c.slug)
1674
1753
  ? chalk.yellow("retired") + chalk.dim(` · synap templates retire ${c.slug}`)
1675
1754
  : installedStatusLabel(c, entryBySlug.get(c.slug)?.isPrivate ?? false, cat.loggedIn);
1676
- console.log(" " + chalk.cyan(c.slug.padEnd(28)) + label);
1755
+ console.log(" " + chalk.cyan(clip(c.slug, slugW - 2).padEnd(slugW)) + label);
1677
1756
  }
1678
1757
  log.blank();
1679
1758
  if (noVersionInfo.length > 0) {
1680
1759
  log.dim(`${noVersionInfo.length} installed without a version stamp — can't check ${noVersionInfo.length === 1 ? "it" : "them"} for updates.`);
1681
1760
  }
1682
- if (degradedNote)
1683
- log.dim(degradedNote);
1684
1761
  }
1685
1762
  // What the JSON carries for the new facts: per slug, the pod's data changes
1686
1763
  // (`null` = this pod sent none: an older pod) and the template lifecycle.
@@ -1722,7 +1799,17 @@ export async function marketUpdate(slugsArg, opts) {
1722
1799
  return;
1723
1800
  }
1724
1801
  if (!shouldApply) {
1725
- const steps = FLOW.afterMarketUpdateCheck(withUpdates.map((c) => c.slug));
1802
+ // Next steps offer only what this command can run: a capability update
1803
+ // (no CLI updater) is the person's act in pod admin, named once below.
1804
+ const steps = FLOW.afterMarketUpdateCheck(cliUpdatableSlugs(withUpdates, { agentKey }));
1805
+ // Same predicate as the apply loop: no CLI door for this kind, or a
1806
+ // pod-admin door for this key (an agent key's skill/view/automation).
1807
+ const personApplies = withUpdates
1808
+ .filter((c) => {
1809
+ const door = updateDoorFor(c.kind, agentKey);
1810
+ return door === null || door === "pod-admin";
1811
+ })
1812
+ .map((c) => c.slug);
1726
1813
  if (opts.json) {
1727
1814
  console.log(JSON.stringify({
1728
1815
  checks,
@@ -1730,6 +1817,9 @@ export async function marketUpdate(slugsArg, opts) {
1730
1817
  unknownSlugs,
1731
1818
  applied: [],
1732
1819
  nextSteps: steps,
1820
+ ...(personApplies.length > 0
1821
+ ? { appliedInPodAdmin: { slugs: personApplies, where: marketUpdatesDoor(cfg.podUrl) } }
1822
+ : {}),
1733
1823
  dataChanges: dataChangesJson,
1734
1824
  templateLifecycle: lifecycleJson,
1735
1825
  }, null, 2));
@@ -1739,6 +1829,9 @@ export async function marketUpdate(slugsArg, opts) {
1739
1829
  if ([...previews.values()].some((pv) => (pv.changes?.length ?? 0) > 0)) {
1740
1830
  log.hint("Updates with data changes ask before they run. To agree up front: --accept-data-changes <opKey,…> (or --yes for all listed)");
1741
1831
  }
1832
+ if (personApplies.length > 0) {
1833
+ log.hint(`${personApplies.join(", ")}: not updated by this command. ${marketUpdatesDoor(cfg.podUrl)}`);
1834
+ }
1742
1835
  renderNextSteps(steps);
1743
1836
  return;
1744
1837
  }
@@ -1748,7 +1841,13 @@ export async function marketUpdate(slugsArg, opts) {
1748
1841
  // at most once, lazily, and read each slug's outcome from its report.
1749
1842
  let configsReport;
1750
1843
  for (const c of withUpdates) {
1751
- const door = updateDoorFor(c.kind);
1844
+ const door = updateDoorFor(c.kind, agentKey);
1845
+ // A capability update (and, for an agent key, a view / skill / automation)
1846
+ // is the person's act in pod admin: not a failure.
1847
+ if (door === "pod-admin" || (door === null && c.kind === "capability")) {
1848
+ results.push(applyInPodAdminResult(c, cfg?.podUrl, opts.json));
1849
+ continue;
1850
+ }
1752
1851
  if (door === null) {
1753
1852
  if (!opts.json)
1754
1853
  log.warn(`${c.slug} is a ${c.kind} — the pod has no per-package update for this kind, so it was not updated. Nothing was written.`);
@@ -1757,7 +1856,7 @@ export async function marketUpdate(slugsArg, opts) {
1757
1856
  }
1758
1857
  if (door === "configs-reconcile") {
1759
1858
  configsReport ??= hubPost("/configs/reconcile", {}, cfg, applyTimeoutMs);
1760
- results.push(await reconcileOutcome(c, configsReport, { json: opts.json }));
1859
+ results.push(await reconcileOutcome(c, configsReport, { json: opts.json, podUrl: cfg?.podUrl }));
1761
1860
  continue;
1762
1861
  }
1763
1862
  const entry = entryBySlug.get(c.slug);
@@ -1834,9 +1933,11 @@ export async function marketUpdate(slugsArg, opts) {
1834
1933
  * already-current, the `*-with-failures` and `"stamp-contradiction"` statuses
1835
1934
  * get their OWN bucket — a partial capability-seed failure or a stamp that
1836
1935
  * silently didn't take is never folded into a clean "updated" count.
1837
- * `"timed-out"` is broken out from the generic `"failed"` bucket; everything
1838
- * else (locked/proposed/fetch-failed/not-in-catalog/no-workspace/unknown)
1839
- * rolls into "failed".
1936
+ * `"timed-out"` is broken out from the generic `"failed"` bucket;
1937
+ * `"proposed"`/`"awaiting-approval"` count as waiting for the person's
1938
+ * approval (never failed); everything else
1939
+ * (locked/fetch-failed/not-in-catalog/no-workspace/unknown) rolls into
1940
+ * "failed".
1840
1941
  */
1841
1942
  export function summarizeApplyResults(results) {
1842
1943
  let updated = 0;
@@ -1846,6 +1947,7 @@ export function summarizeApplyResults(results) {
1846
1947
  let failed = 0;
1847
1948
  let proposed = 0;
1848
1949
  let needsConsent = 0;
1950
+ let toApplyInPodAdmin = 0;
1849
1951
  for (const r of results) {
1850
1952
  switch (r.status) {
1851
1953
  case "installed":
@@ -1880,17 +1982,23 @@ export function summarizeApplyResults(results) {
1880
1982
  case "needs-consent":
1881
1983
  needsConsent++;
1882
1984
  break;
1985
+ // The person's act (pod admin → Marketplace → Updates): nothing failed.
1986
+ case APPLY_IN_POD_ADMIN:
1987
+ toApplyInPodAdmin++;
1988
+ break;
1883
1989
  default:
1884
1990
  failed++;
1885
1991
  }
1886
1992
  }
1887
1993
  const parts = [`${updated} updated`, `${unchanged} already current`];
1888
1994
  if (proposed > 0)
1889
- parts.push(`${proposed} proposed (awaiting review)`);
1995
+ parts.push(`${proposed} waiting for your approval`);
1890
1996
  // The vocabulary's `unfinished` ("Not finished"): one state, one name on
1891
1997
  // every surface — never "completed with failures" (review2 UX §1 #2).
1892
1998
  if (withFailures > 0)
1893
1999
  parts.push(`${withFailures} ${templateStepPhrase("unfinished")}`);
2000
+ if (toApplyInPodAdmin > 0)
2001
+ parts.push(`${toApplyInPodAdmin} to apply in pod admin`);
1894
2002
  if (failed > 0)
1895
2003
  parts.push(`${failed} failed`);
1896
2004
  if (timedOut > 0)
@@ -1899,9 +2007,9 @@ export function summarizeApplyResults(results) {
1899
2007
  parts.push(`${needsConsent} waiting for your agreement to data changes`);
1900
2008
  const clean = withFailures === 0 && failed === 0 && timedOut === 0 && needsConsent === 0;
1901
2009
  // Proposed isn't a failure (never ⚠) but isn't live yet (not a bare ✓) —
1902
- // a distinct "→ awaiting review" marker keeps the summary honest.
1903
- const marker = !clean ? "⚠" : proposed > 0 ? "→" : "✓";
1904
- const color = !clean ? chalk.yellow : proposed > 0 ? chalk.cyan : chalk.green;
2010
+ // a distinct "→ waiting for your approval" marker keeps the summary honest.
2011
+ const marker = !clean ? "⚠" : proposed > 0 || toApplyInPodAdmin > 0 ? "→" : "✓";
2012
+ const color = !clean ? chalk.yellow : proposed > 0 || toApplyInPodAdmin > 0 ? chalk.cyan : chalk.green;
1905
2013
  return {
1906
2014
  updated,
1907
2015
  unchanged,
@@ -1910,6 +2018,7 @@ export function summarizeApplyResults(results) {
1910
2018
  timedOut,
1911
2019
  proposed,
1912
2020
  needsConsent,
2021
+ toApplyInPodAdmin,
1913
2022
  line: color(`${marker} ${parts.join(" · ")}`),
1914
2023
  };
1915
2024
  }
@@ -2389,7 +2498,7 @@ export async function marketInstalled(opts) {
2389
2498
  const entryBySlug = new Map(cat.entries.map((e) => [e.slug, e]));
2390
2499
  if (opts.layers) {
2391
2500
  const graph = await buildInstallationGraph(installedTemplates, cat, checks);
2392
- const steps = FLOW.afterMarketInstalledCheck(rows.filter((c) => c.updateAvailable).map((c) => c.slug));
2501
+ const steps = FLOW.afterMarketInstalledCheck(cliUpdatableSlugs(rows));
2393
2502
  if (opts.json) {
2394
2503
  console.log(JSON.stringify({
2395
2504
  nodes: [...graph.nodes.values()],
@@ -2416,7 +2525,7 @@ export async function marketInstalled(opts) {
2416
2525
  }
2417
2526
  if (opts.tree) {
2418
2527
  const { packages, leafRows } = await buildCompositionTrees(rows, cat, installedTemplates, workspaceCountBySlug);
2419
- const steps = FLOW.afterMarketInstalledCheck(rows.filter((c) => c.updateAvailable).map((c) => c.slug));
2528
+ const steps = FLOW.afterMarketInstalledCheck(cliUpdatableSlugs(rows));
2420
2529
  if (opts.json) {
2421
2530
  console.log(JSON.stringify({
2422
2531
  packages,
@@ -2476,7 +2585,7 @@ export async function marketInstalled(opts) {
2476
2585
  return;
2477
2586
  }
2478
2587
  if (opts.json) {
2479
- const steps = FLOW.afterMarketInstalledCheck(rows.filter((c) => c.updateAvailable).map((c) => c.slug));
2588
+ const steps = FLOW.afterMarketInstalledCheck(cliUpdatableSlugs(rows));
2480
2589
  console.log(JSON.stringify({
2481
2590
  rows: rows.map((c) => {
2482
2591
  const entry = entryBySlug.get(c.slug);
@@ -2594,7 +2703,7 @@ export async function marketInstalled(opts) {
2594
2703
  log.dim(degradedNote);
2595
2704
  if (!cat.loggedIn)
2596
2705
  log.dim("Not logged in — private template updates aren't visible. Run: synap login");
2597
- renderNextSteps(FLOW.afterMarketInstalledCheck(rows.filter((c) => c.updateAvailable).map((c) => c.slug)));
2706
+ renderNextSteps(FLOW.afterMarketInstalledCheck(cliUpdatableSlugs(rows)));
2598
2707
  }
2599
2708
  /** Classify one workspace's attachment, reusing `computeUpdates`' drift verdict for the stamped case. */
2600
2709
  function classifyAttachment(ws, check) {