cognetivy 1.0.2 → 2.0.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 (251) hide show
  1. package/dist/cli.js +415 -1276
  2. package/dist/cli.js.map +1 -1
  3. package/dist/cloud-client.d.ts +27 -1
  4. package/dist/cloud-client.d.ts.map +1 -1
  5. package/dist/cloud-client.js +18 -3
  6. package/dist/cloud-client.js.map +1 -1
  7. package/dist/core/collection-minimal-stub.d.ts +17 -0
  8. package/dist/core/collection-minimal-stub.d.ts.map +1 -0
  9. package/dist/core/collection-minimal-stub.js +124 -0
  10. package/dist/core/collection-minimal-stub.js.map +1 -0
  11. package/dist/core/collection-validate.d.ts +5 -0
  12. package/dist/core/collection-validate.d.ts.map +1 -1
  13. package/dist/core/collection-validate.js +68 -0
  14. package/dist/core/collection-validate.js.map +1 -1
  15. package/dist/core/next-step-engine.d.ts.map +1 -1
  16. package/dist/core/next-step-engine.js +66 -3
  17. package/dist/core/next-step-engine.js.map +1 -1
  18. package/dist/core/types.d.ts +9 -0
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/dist/core/types.js.map +1 -1
  21. package/dist/core/workflow-validate.d.ts.map +1 -1
  22. package/dist/core/workflow-validate.js +11 -0
  23. package/dist/core/workflow-validate.js.map +1 -1
  24. package/dist/dev-checkout.d.ts +13 -0
  25. package/dist/dev-checkout.d.ts.map +1 -0
  26. package/dist/dev-checkout.js +56 -0
  27. package/dist/dev-checkout.js.map +1 -0
  28. package/dist/edge-executor-spawn.d.ts +22 -0
  29. package/dist/edge-executor-spawn.d.ts.map +1 -0
  30. package/dist/edge-executor-spawn.js +122 -0
  31. package/dist/edge-executor-spawn.js.map +1 -0
  32. package/dist/edge-worker.d.ts +7 -0
  33. package/dist/edge-worker.d.ts.map +1 -0
  34. package/dist/edge-worker.js +210 -0
  35. package/dist/edge-worker.js.map +1 -0
  36. package/dist/executor/abort-signals.d.ts +5 -0
  37. package/dist/executor/abort-signals.d.ts.map +1 -0
  38. package/dist/executor/abort-signals.js +18 -0
  39. package/dist/executor/abort-signals.js.map +1 -0
  40. package/dist/executor/agent-node-runner.d.ts +34 -0
  41. package/dist/executor/agent-node-runner.d.ts.map +1 -0
  42. package/dist/executor/agent-node-runner.js +454 -0
  43. package/dist/executor/agent-node-runner.js.map +1 -0
  44. package/dist/executor/claude-code-stdio-protocol.d.ts +9 -0
  45. package/dist/executor/claude-code-stdio-protocol.d.ts.map +1 -0
  46. package/dist/executor/claude-code-stdio-protocol.js +114 -0
  47. package/dist/executor/claude-code-stdio-protocol.js.map +1 -0
  48. package/dist/executor/claude-stream-json-line.d.ts +23 -0
  49. package/dist/executor/claude-stream-json-line.d.ts.map +1 -0
  50. package/dist/executor/claude-stream-json-line.js +177 -0
  51. package/dist/executor/claude-stream-json-line.js.map +1 -0
  52. package/dist/executor/codex-jsonl-stream.d.ts +14 -0
  53. package/dist/executor/codex-jsonl-stream.d.ts.map +1 -0
  54. package/dist/executor/codex-jsonl-stream.js +104 -0
  55. package/dist/executor/codex-jsonl-stream.js.map +1 -0
  56. package/dist/executor/collection-output-helpers.d.ts +15 -0
  57. package/dist/executor/collection-output-helpers.d.ts.map +1 -0
  58. package/dist/executor/collection-output-helpers.js +67 -0
  59. package/dist/executor/collection-output-helpers.js.map +1 -0
  60. package/dist/executor/hitl-wait-with-cloud-poll.d.ts +17 -0
  61. package/dist/executor/hitl-wait-with-cloud-poll.d.ts.map +1 -0
  62. package/dist/executor/hitl-wait-with-cloud-poll.js +72 -0
  63. package/dist/executor/hitl-wait-with-cloud-poll.js.map +1 -0
  64. package/dist/executor/json-extract.d.ts +22 -0
  65. package/dist/executor/json-extract.d.ts.map +1 -0
  66. package/dist/executor/json-extract.js +127 -0
  67. package/dist/executor/json-extract.js.map +1 -0
  68. package/dist/executor/node-workspace-isolation.d.ts +3 -0
  69. package/dist/executor/node-workspace-isolation.d.ts.map +1 -0
  70. package/dist/executor/node-workspace-isolation.js +79 -0
  71. package/dist/executor/node-workspace-isolation.js.map +1 -0
  72. package/dist/executor/prompt-for-node.d.ts +4 -0
  73. package/dist/executor/prompt-for-node.d.ts.map +1 -0
  74. package/dist/executor/prompt-for-node.js +54 -0
  75. package/dist/executor/prompt-for-node.js.map +1 -0
  76. package/dist/executor/workflow-executor.d.ts +21 -0
  77. package/dist/executor/workflow-executor.d.ts.map +1 -0
  78. package/dist/executor/workflow-executor.js +218 -0
  79. package/dist/executor/workflow-executor.js.map +1 -0
  80. package/dist/executor/workflow-generate-prompt.d.ts +17 -0
  81. package/dist/executor/workflow-generate-prompt.d.ts.map +1 -0
  82. package/dist/executor/workflow-generate-prompt.js +60 -0
  83. package/dist/executor/workflow-generate-prompt.js.map +1 -0
  84. package/dist/executor/workflow-generate-runner.d.ts +21 -0
  85. package/dist/executor/workflow-generate-runner.d.ts.map +1 -0
  86. package/dist/executor/workflow-generate-runner.js +313 -0
  87. package/dist/executor/workflow-generate-runner.js.map +1 -0
  88. package/dist/executor/workflow-node-execution.d.ts +34 -0
  89. package/dist/executor/workflow-node-execution.d.ts.map +1 -0
  90. package/dist/executor/workflow-node-execution.js +207 -0
  91. package/dist/executor/workflow-node-execution.js.map +1 -0
  92. package/dist/install-tui.d.ts +2 -3
  93. package/dist/install-tui.d.ts.map +1 -1
  94. package/dist/install-tui.js +33 -40
  95. package/dist/install-tui.js.map +1 -1
  96. package/dist/local-db/execution-store.d.ts +12 -0
  97. package/dist/local-db/execution-store.d.ts.map +1 -0
  98. package/dist/local-db/execution-store.js +79 -0
  99. package/dist/local-db/execution-store.js.map +1 -0
  100. package/dist/local-hitl-command.d.ts +7 -0
  101. package/dist/local-hitl-command.d.ts.map +1 -0
  102. package/dist/local-hitl-command.js +83 -0
  103. package/dist/local-hitl-command.js.map +1 -0
  104. package/dist/local-server/executor-terminal-log.d.ts +20 -0
  105. package/dist/local-server/executor-terminal-log.d.ts.map +1 -0
  106. package/dist/local-server/executor-terminal-log.js +143 -0
  107. package/dist/local-server/executor-terminal-log.js.map +1 -0
  108. package/dist/local-server/hitl-coordinator.d.ts +17 -0
  109. package/dist/local-server/hitl-coordinator.d.ts.map +1 -0
  110. package/dist/local-server/hitl-coordinator.js +33 -0
  111. package/dist/local-server/hitl-coordinator.js.map +1 -0
  112. package/dist/local-server/local-executor-session.d.ts +14 -0
  113. package/dist/local-server/local-executor-session.d.ts.map +1 -0
  114. package/dist/local-server/local-executor-session.js +49 -0
  115. package/dist/local-server/local-executor-session.js.map +1 -0
  116. package/dist/local-server/local-studio-server.d.ts +14 -0
  117. package/dist/local-server/local-studio-server.d.ts.map +1 -0
  118. package/dist/local-server/local-studio-server.js +290 -0
  119. package/dist/local-server/local-studio-server.js.map +1 -0
  120. package/dist/local-server/static-root.d.ts +8 -0
  121. package/dist/local-server/static-root.d.ts.map +1 -0
  122. package/dist/local-server/static-root.js +30 -0
  123. package/dist/local-server/static-root.js.map +1 -0
  124. package/dist/local-server/ws-protocol.d.ts +89 -0
  125. package/dist/local-server/ws-protocol.d.ts.map +1 -0
  126. package/dist/local-server/ws-protocol.js +8 -0
  127. package/dist/local-server/ws-protocol.js.map +1 -0
  128. package/dist/local-studio/assets/build.umd-BMMVnbyk.js +7 -0
  129. package/dist/local-studio/assets/index-B4zVqOUT.js +1 -0
  130. package/dist/local-studio/assets/index-CjYihVZf.js +1612 -0
  131. package/dist/local-studio/assets/index-url63DuJ.css +1 -0
  132. package/dist/{studio/assets/index.es-BY7s66E3.js → local-studio/assets/index.es-CNYpHgAn.js} +3 -3
  133. package/dist/local-studio/assets/jspdf.es.min-ZRtIKKtw.js +79 -0
  134. package/dist/local-studio/assets/purify.es-C0_7NiBM.js +2 -0
  135. package/dist/local-studio/index.html +27 -0
  136. package/dist/local-studio-entry.d.ts +14 -0
  137. package/dist/local-studio-entry.d.ts.map +1 -0
  138. package/dist/local-studio-entry.js +60 -0
  139. package/dist/local-studio-entry.js.map +1 -0
  140. package/dist/local-studio-placeholder/index.html +58 -0
  141. package/dist/mcp.d.ts +1 -2
  142. package/dist/mcp.d.ts.map +1 -1
  143. package/dist/mcp.js +17 -956
  144. package/dist/mcp.js.map +1 -1
  145. package/dist/models.d.ts +0 -1
  146. package/dist/models.d.ts.map +1 -1
  147. package/dist/models.js.map +1 -1
  148. package/dist/nest-proxy.d.ts +12 -0
  149. package/dist/nest-proxy.d.ts.map +1 -0
  150. package/dist/nest-proxy.js +69 -0
  151. package/dist/nest-proxy.js.map +1 -0
  152. package/dist/run-engine.d.ts +1 -22
  153. package/dist/run-engine.d.ts.map +1 -1
  154. package/dist/run-engine.js +5 -75
  155. package/dist/run-engine.js.map +1 -1
  156. package/dist/skills.d.ts +1 -3
  157. package/dist/skills.d.ts.map +1 -1
  158. package/dist/skills.js +19 -108
  159. package/dist/skills.js.map +1 -1
  160. package/dist/studio-server.d.ts.map +1 -1
  161. package/dist/studio-server.js +23 -0
  162. package/dist/studio-server.js.map +1 -1
  163. package/dist/workflow-template-apply.d.ts +0 -14
  164. package/dist/workflow-template-apply.d.ts.map +1 -1
  165. package/dist/workflow-template-apply.js +1 -64
  166. package/dist/workflow-template-apply.js.map +1 -1
  167. package/dist/workspace.d.ts +6 -118
  168. package/dist/workspace.d.ts.map +1 -1
  169. package/dist/workspace.js +63 -419
  170. package/dist/workspace.js.map +1 -1
  171. package/package.json +11 -7
  172. package/dist/studio/assets/index-5HXAsU4p.js +0 -74
  173. package/dist/studio/assets/index-B1aL7jAk.js +0 -57
  174. package/dist/studio/assets/index-B1uz6Ewl.css +0 -1
  175. package/dist/studio/assets/index-B2G_F__P.css +0 -1
  176. package/dist/studio/assets/index-BK6xaJ13.js +0 -96
  177. package/dist/studio/assets/index-BMhv4sXY.js +0 -96
  178. package/dist/studio/assets/index-BO2PmdDj.css +0 -1
  179. package/dist/studio/assets/index-B_0ldjZP.js +0 -96
  180. package/dist/studio/assets/index-Bbhu9ZEt.css +0 -1
  181. package/dist/studio/assets/index-BjW5ogyU.js +0 -96
  182. package/dist/studio/assets/index-Bt8hzNcU.js +0 -96
  183. package/dist/studio/assets/index-BzAnUkaS.js +0 -74
  184. package/dist/studio/assets/index-C0crAUM8.css +0 -1
  185. package/dist/studio/assets/index-CHc9YG3r.js +0 -112
  186. package/dist/studio/assets/index-CKtCWKy5.js +0 -96
  187. package/dist/studio/assets/index-CSIfnKun.css +0 -1
  188. package/dist/studio/assets/index-CXZmAIuY.js +0 -91
  189. package/dist/studio/assets/index-Cbvah-pg.js +0 -96
  190. package/dist/studio/assets/index-Cd1ymjRj.js +0 -96
  191. package/dist/studio/assets/index-CrMnnQO-.css +0 -1
  192. package/dist/studio/assets/index-D1J5_8Th.js +0 -91
  193. package/dist/studio/assets/index-DCT2826T.css +0 -1
  194. package/dist/studio/assets/index-DI1FMf0r.js +0 -57
  195. package/dist/studio/assets/index-DONQugvM.js +0 -96
  196. package/dist/studio/assets/index-DTK0SCwL.css +0 -1
  197. package/dist/studio/assets/index-DZpzYO63.js +0 -96
  198. package/dist/studio/assets/index-DmV3GQK3.css +0 -1
  199. package/dist/studio/assets/index-DmntCnBe.js +0 -96
  200. package/dist/studio/assets/index-DoIh_odG.js +0 -57
  201. package/dist/studio/assets/index-Dq9jftDf.js +0 -91
  202. package/dist/studio/assets/index-DvzphmLm.css +0 -1
  203. package/dist/studio/assets/index-SPD_jpsc.css +0 -1
  204. package/dist/studio/assets/index-ZiR_njv1.js +0 -96
  205. package/dist/studio/assets/index-ctxo2E0F.css +0 -1
  206. package/dist/studio/assets/index-fTm91Iph.css +0 -1
  207. package/dist/studio/assets/index-jQR0RL1o.js +0 -96
  208. package/dist/studio/assets/index-uHQLfD-r.css +0 -1
  209. package/dist/studio/assets/index.es-BCWxGaDy.js +0 -5
  210. package/dist/studio/assets/index.es-BPR3wmCH.js +0 -5
  211. package/dist/studio/assets/index.es-BW1yZSTk.js +0 -5
  212. package/dist/studio/assets/index.es-BgB1THzs.js +0 -5
  213. package/dist/studio/assets/index.es-C1dpmutL.js +0 -5
  214. package/dist/studio/assets/index.es-C4vElYon.js +0 -5
  215. package/dist/studio/assets/index.es-CA5y2tVx.js +0 -5
  216. package/dist/studio/assets/index.es-CJoNdv-d.js +0 -5
  217. package/dist/studio/assets/index.es-DJXBkJhb.js +0 -5
  218. package/dist/studio/assets/index.es-DhJ1KIdk.js +0 -5
  219. package/dist/studio/assets/index.es-DoO31ZM_.js +0 -5
  220. package/dist/studio/assets/index.es-Y3KhW381.js +0 -5
  221. package/dist/studio/assets/index.es-_wiJglk0.js +0 -5
  222. package/dist/studio/assets/index.es-dcgfUrS8.js +0 -5
  223. package/dist/studio/assets/index.es-j2tEOPI7.js +0 -5
  224. package/dist/studio/assets/index.es-zcnPOBL8.js +0 -5
  225. package/dist/studio/assets/jspdf.es.min-4bkGOR48.js +0 -79
  226. package/dist/studio/assets/jspdf.es.min-B3_vfH3S.js +0 -79
  227. package/dist/studio/assets/jspdf.es.min-B7A3pncP.js +0 -79
  228. package/dist/studio/assets/jspdf.es.min-B8ciqKwO.js +0 -79
  229. package/dist/studio/assets/jspdf.es.min-BWXErOTt.js +0 -79
  230. package/dist/studio/assets/jspdf.es.min-Be57pgIy.js +0 -79
  231. package/dist/studio/assets/jspdf.es.min-C-v2XVac.js +0 -79
  232. package/dist/studio/assets/jspdf.es.min-CTaJgZ0U.js +0 -79
  233. package/dist/studio/assets/jspdf.es.min-CeBUfwhn.js +0 -79
  234. package/dist/studio/assets/jspdf.es.min-Ctp5RK7Z.js +0 -79
  235. package/dist/studio/assets/jspdf.es.min-D4KCUvlV.js +0 -79
  236. package/dist/studio/assets/jspdf.es.min-DCNsC1_p.js +0 -79
  237. package/dist/studio/assets/jspdf.es.min-DMFDYk4G.js +0 -79
  238. package/dist/studio/assets/jspdf.es.min-DP4SG4JX.js +0 -79
  239. package/dist/studio/assets/jspdf.es.min-DmeLTeJx.js +0 -79
  240. package/dist/studio/assets/jspdf.es.min-DtNe0aer.js +0 -79
  241. package/dist/studio/assets/jspdf.es.min-SPz-8IlP.js +0 -79
  242. package/dist/studio/assets/purify.es-Bzr520pe.js +0 -2
  243. package/dist/studio/index.html +0 -14
  244. /package/dist/{studio → local-studio}/assets/html2canvas.esm-C5Fx4D8v.js +0 -0
  245. /package/dist/{studio → local-studio}/assets/html2canvas.esm-DXEQVQnt.js +0 -0
  246. /package/dist/{studio → local-studio}/assets/marked.esm-Brtrz5BF.js +0 -0
  247. /package/dist/{studio → local-studio}/favicon.png +0 -0
  248. /package/dist/{studio → local-studio}/icon-pixelized.png +0 -0
  249. /package/dist/{studio → local-studio}/icon-pixelized2.png +0 -0
  250. /package/dist/{studio → local-studio}/icon.jpg +0 -0
  251. /package/dist/{studio → local-studio}/vite.svg +0 -0
package/dist/cli.js CHANGED
@@ -2,17 +2,14 @@
2
2
  import { program } from "commander";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
- import { ensureMinimalWorkspace, ensureWorkspace, isWorkspaceMinimal, requireWorkspace, workspaceExists, readWorkflowIndex, readWorkflowIndexOptional, writeWorkflowIndex, listWorkflows, readWorkflowRecord, writeWorkflowRecord, listWorkflowVersionIds, readWorkflowVersionRecord, writeWorkflowVersionRecord, writeRunFile, readRunFile, updateRunFile, appendEventLine, runExists, readCollectionSchema, writeCollectionSchema, listCollectionKindsForRun, readCollections, writeCollections, appendCollection, listNodeResults, readNodeResult, writeNodeResult, deleteCollectionItemsByIds, } from "./workspace.js";
5
+ import { ensureMinimalWorkspace, readWorkflowIndex, readWorkflowIndexOptional, writeWorkflowIndex, } from "./workspace.js";
6
6
  import { getMergedConfig } from "./config.js";
7
- import { validateWorkflowVersion } from "./validate.js";
8
- import { getNextStep, formatNextStepLine } from "./run-engine.js";
7
+ import { formatNextStepLine } from "./run-engine.js";
9
8
  import { mergeKindTemplate } from "./kind-templates.js";
10
9
  import { listWorkflowTemplates, listWorkflowTemplatesForPicker, materializeWorkflowTemplate } from "./workflow-templates.js";
11
- import { applyWorkflowTemplateToWorkspace, applyWorkflowTemplateToCloud } from "./workflow-template-apply.js";
12
- import { NodeResultStatus } from "./models.js";
10
+ import { applyWorkflowTemplateToCloud } from "./workflow-template-apply.js";
13
11
  import { runMcpServer } from "./mcp.js";
14
- import { startStudioServer, STUDIO_DEFAULT_PORT } from "./studio-server.js";
15
- import { cloudCreateRun, cloudGetRun, cloudGetNext, cloudStartNode, cloudCompleteNode, cloudAppendEvents, mapCloudActionToLocal, isCloudMode, isCloudAuthenticated, getCloudApiUrl, cloudGetCurrentUser, resolveCloudOrganizationId, cloudListWorkflows, cloudCreateWorkflow, cloudCreateWorkflowFull, cloudCreateWorkflowVersion, cloudGetWorkflow, cloudGetWorkflowVersions, cloudGetWorkflowVersion, cloudListCollectionKinds, cloudGetCollectionItems, cloudSetCollectionSchema, } from "./cloud-client.js";
12
+ import { cloudCreateRun, cloudGetRun, cloudGetNext, cloudStartNode, cloudCompleteNode, cloudAppendEvents, mapCloudActionToCliAction, isCloudMode, isCloudAuthenticated, getCloudApiUrl, cloudGetCurrentUser, resolveCloudOrganizationId, cloudListWorkflows, cloudCreateWorkflow, cloudCreateWorkflowFull, cloudCreateWorkflowVersion, cloudGetWorkflow, cloudGetWorkflowVersions, cloudGetWorkflowVersion, cloudListCollectionKinds, cloudGetCollectionItems, cloudSetCollectionSchema, cloudGetCollectionSchema, } from "./cloud-client.js";
16
13
  import { writeStoredApiKey, removeStoredApiKey, getApiKeyPath } from "./credentials.js";
17
14
  import { runLoginFlow } from "./auth-login-server.js";
18
15
  import open from "open";
@@ -66,13 +63,11 @@ import updateNotifier from "update-notifier";
66
63
  import * as p from "@clack/prompts";
67
64
  import { openCliDocsInBrowser } from "./cli-docs.js";
68
65
  import { getCloudAppUrl, buildCloudOnboardingUrl } from "./onboarding-url.js";
66
+ import { runLocalStudioForeground, resolveLocalStudioBrowserBase } from "./local-studio-entry.js";
67
+ import { createLocalStudioServer } from "./local-server/local-studio-server.js";
68
+ import { resolveLocalStudioStaticRoot, localStudioBundleHasCliAuth } from "./local-server/static-root.js";
69
69
  import { parsePayload, formatFromFilePath, stringifyPayload } from "./payload-parse.js";
70
70
  const DEFAULT_BY = "cli";
71
- function generateId(prefix) {
72
- const ts = new Date().toISOString().replace(/[:.]/g, "-");
73
- const rand = Math.random().toString(36).slice(2, 8);
74
- return `${prefix}_${ts}_${rand}`;
75
- }
76
71
  async function resolveBy(cwd) {
77
72
  const config = await getMergedConfig(cwd);
78
73
  return config.default_by ?? DEFAULT_BY;
@@ -86,16 +81,11 @@ async function resolveCloudWorkflowId(cwd, optsWorkflow) {
86
81
  const index = await readWorkflowIndexOptional(cwd);
87
82
  return index?.cloud_current_workflow_id ?? null;
88
83
  }
89
- /** Resolve whether to use cloud API: --local → false, --cloud → true, else preferred_mode or isCloudMode(). */
90
- async function resolveUseCloud(cwd, opts) {
91
- if (opts.local)
92
- return false;
93
- if (opts.cloud !== undefined && opts.cloud !== null)
94
- return opts.cloud;
95
- const index = await readWorkflowIndexOptional(cwd);
96
- if (index?.preferred_mode === "local")
97
- return false;
98
- return isCloudMode();
84
+ async function requireCloudApiKey() {
85
+ if (!isCloudMode()) {
86
+ console.error("Error: Cloud API key required. Run `cognetivy auth login` or set COGNETIVY_API_KEY.");
87
+ process.exit(1);
88
+ }
99
89
  }
100
90
  /** Extract all unique collection names from nodes (input_collections + output_collections). */
101
91
  function getCollectionNamesFromNodes(nodes) {
@@ -135,42 +125,59 @@ async function readPayloadFromFileOrStdin(filePath, cwd) {
135
125
  }
136
126
  return raw;
137
127
  }
138
- /** Launch local Studio server and open in browser (for local .cognetivy workspace). Optionally deep-link to a workflow. */
139
- async function launchLocalStudio(workspacePath, port = STUDIO_DEFAULT_PORT, workflowId) {
140
- await requireWorkspace(workspacePath);
141
- const { port: actualPort } = await startStudioServer(workspacePath, port, { apiOnly: false });
142
- const base = `http://127.0.0.1:${actualPort}`;
143
- const url = workflowId ? `${base}/workflows/${encodeURIComponent(workflowId)}` : base;
144
- await openUrl(url);
145
- console.log(`Local Studio at ${base} (workspace: ${workspacePath}). Press Ctrl+C to stop.`);
146
- }
147
128
  const DEV_API_URL = "http://localhost:3000";
148
129
  /** Local cloud-studio dev server; used for auth/login when --dev. */
149
130
  const DEV_APP_URL = "http://localhost:5174";
150
131
  program
151
132
  .name("cognetivy")
152
- .description("Cognetivy – workflows, runs, and collections. Default: open the app in your browser. Use `cognetivy auth status` to check API key; `cognetivy auth login` to sign in and get an API key.")
133
+ .description("Cognetivy – workflows, runs, and collections. Default: start the local studio backend (browser + executor). Use `cognetivy auth status` to check API key; `cognetivy auth login` to sign in and get an API key.")
153
134
  .version(getCurrentVersionSync())
154
135
  .option("--interface", "Open CLI reference in browser (same as `cognetivy docs`)")
155
- .option("--dev", "Use local backend (API http://localhost:3000, app http://localhost:5174). Run backend and cloud-studio locally first.")
136
+ .option("--dev", "Point at local backend: API http://localhost:3000 and app http://localhost:5174. Opens the Vite dev studio (same port) with ?session=…; run `npm run dev` in cloud-studio first. Proxy /ws and /api/local to the CLI (see cloud-studio vite.config). Set COGNETIVY_USE_BUNDLED_STUDIO=1 to open the bundled UI on port 3848 instead.")
137
+ .option("--api-url <url>", "Backend API base for this run only (overrides COGNETIVY_API_URL). Local studio CLI auth uses this URL. Example: --api-url http://localhost:3000")
156
138
  .addHelpText("after", `
157
139
  Environment (cloud):
158
140
  COGNETIVY_API_KEY API key for cloud run/event (create at app → Settings). When set, run/event use cloud by default.
159
141
  COGNETIVY_APP_URL URL opened by default command (default: https://alpha.cognetivy.com).
160
- COGNETIVY_API_URL Cloud API base URL (default: http://localhost:3000 in dev, https://bm.cognetivy.com otherwise). Use for local backend or custom deployment.
142
+ COGNETIVY_API_URL Backend API base (default: https://bm.cognetivy.com, or http://localhost:3000 when NODE_ENV=development / COGNETIVY_DEV=1). Local studio injects this into the page so /auth/cli/authorize matches the CLI token exchange.
143
+
144
+ Local studio (default command):
145
+ COGNETIVY_LOCAL_PORT Bind port for local HTTP + WebSocket (default: 3848).
146
+ COGNETIVY_LOCAL_STUDIO_URL Open this origin with ?session=… (e.g. http://localhost:5174 for Vite HMR). Vite proxies /ws and /api/local to the CLI. With cognetivy --dev, defaults to http://localhost:5174 unless set; COGNETIVY_USE_BUNDLED_STUDIO=1 opens the bundled UI on COGNETIVY_LOCAL_PORT instead.
147
+ COGNETIVY_OPEN_APP Set to 0 or false to skip opening the browser.
148
+ COGNETIVY_EXECUTOR_LOG Set to 0 or false to hide executor status lines on stderr (run phases, nodes, HITL; not agent tool output).
149
+ COGNETIVY_WORKFLOW_GENERATE_DEBUG Set to 1 — stderr diagnostics for “Generate workflow” (marker counts, combinedLog, parse/validation, cloud create timing).
150
+ COGNETIVY_WORKFLOW_GENERATE_HEARTBEAT_SEC Interval (seconds) for “agent still running …” lines while Generate workflow waits on the child (default 20; min 5).
151
+ COGNETIVY_CLAUDE_STREAM_JSON_IDLE_END_MS Claude stream-json: ms of stdout silence after a COGNETIVY_* payload marker before closing stdin (default 6000) if the CLI never sends a terminal result line.
152
+ COGNETIVY_AGENT_COMBINED_LOG_MAX_CHARS Max characters of merged agent stdout kept for marker parsing (default 1500000); when trimming, prefers retaining text from the last COGNETIVY_COLLECTION_JSON= / COGNETIVY_WORKFLOW_FILE_JSON=.
153
+ COGNETIVY_PARALLEL_ISOLATION copy (default) or none — per parallel PROMPT node, copy workspace into .cognetivy/exec-islands/<run>/<node>/ (skips node_modules, .git, dist, …) or share the parent cwd.
161
154
 
162
- Use \`cognetivy auth status\` to see current auth and URLs. Use \`--local\` on run/event to force local workspace when API key is set. Use \`--dev\` to point cloud at http://localhost:3000.
155
+ Examples: \`cognetivy --dev\` (local API), \`cognetivy --api-url http://127.0.0.1:3000\`. Use \`cognetivy auth status\` to see resolved URLs.
163
156
  `);
164
157
  program.hook("preAction", () => {
165
158
  const opts = program.opts();
166
- if (opts.dev) {
159
+ const trimmedApi = typeof opts.apiUrl === "string" ? opts.apiUrl.trim() : "";
160
+ if (trimmedApi) {
161
+ process.env.COGNETIVY_API_URL = trimmedApi.replace(/\/$/, "");
162
+ }
163
+ else if (opts.dev) {
167
164
  process.env.COGNETIVY_API_URL = DEV_API_URL;
168
165
  process.env.COGNETIVY_APP_URL = DEV_APP_URL;
166
+ const useBundledStudio = process.env.COGNETIVY_USE_BUNDLED_STUDIO === "1" ||
167
+ process.env.COGNETIVY_USE_BUNDLED_STUDIO === "true";
168
+ const existingStudioUrl = (process.env.COGNETIVY_LOCAL_STUDIO_URL ?? "").trim();
169
+ if (!useBundledStudio && !existingStudioUrl) {
170
+ process.env.COGNETIVY_LOCAL_STUDIO_URL = DEV_APP_URL;
171
+ }
169
172
  }
170
173
  });
171
174
  const authCmd = program
172
175
  .command("auth")
173
- .description("Authentication and API key. Run with no subcommand to see: status, login, logout, whoami.");
176
+ .description("Authentication and API key. Run with no subcommand to see: status, login, logout, whoami.")
177
+ .addHelpText("after", `
178
+ For a local Nest API, put global options first: cognetivy --dev auth login
179
+ or: cognetivy --api-url http://localhost:3000 auth login
180
+ `);
174
181
  authCmd
175
182
  .command("status")
176
183
  .description("Show whether cloud API key is set and which app/API URLs are used. Run with no options for human-readable output; use --json for machine-readable.")
@@ -203,7 +210,7 @@ authCmd
203
210
  });
204
211
  authCmd
205
212
  .command("login")
206
- .description("Open the app in browser to sign in and authorize the CLI. Saves API key locally; no other options required.")
213
+ .description("Open the app (or local studio) to sign in and authorize the CLI. Saves API key locally. Use cognetivy --dev auth login to use http://localhost:3000.")
207
214
  .action(async () => {
208
215
  const appUrl = getCloudAppUrl();
209
216
  console.log("Opening browser to sign in and authorize the CLI…");
@@ -322,131 +329,21 @@ program
322
329
  .command("init")
323
330
  .description("Initialize .cognetivy workspace and run interactive skill installer (same as cognetivy install). Use --workspace-only to only create the workspace.")
324
331
  .option("--no-gitignore", "Do not add .gitignore snippet for runs/events/collections")
325
- .option("--force", "Re-init: overwrite workflow pointer and default version if present")
332
+ .option("--force", "Passed to skill install when using the interactive installer (overwrite existing skills)")
326
333
  .option("--workspace-only", "Only create .cognetivy workspace; do not prompt for skill installation")
327
334
  .action(async (opts) => {
328
335
  const cwd = process.cwd();
329
336
  const noGitignore = opts.gitignore === false;
330
337
  if (opts.workspaceOnly) {
331
- await ensureWorkspace(cwd, { force: opts.force, noGitignore });
338
+ await ensureMinimalWorkspace(cwd, { noGitignore });
332
339
  console.log("Initialized cognetivy workspace at .cognetivy/");
333
340
  return;
334
341
  }
335
342
  const { runInstallTUI } = await import("./install-tui.js");
336
343
  await runInstallTUI({ cwd, force: opts.force, init: true, noGitignore });
337
- await launchLocalStudio(cwd);
338
- });
339
- program
340
- .command("mode")
341
- .description("Set or show default mode: Cloud (app + API) or Local (this machine only). Use with no options to switch interactively; --show for current state; --select for non-interactive.")
342
- .option("--show", "Show current mode and workspace type (no prompt)")
343
- .option("--json", "Output machine-readable JSON")
344
- .option("--select <mode>", "Set mode without prompt (cloud | local). For scripts and tests.")
345
- .action(async (opts) => {
346
- const cwd = process.cwd();
347
- const showOnly = opts.show === true || opts.json === true;
348
- const selectMode = opts.select === "cloud" || opts.select === "local" ? opts.select : null;
349
- const hadWorkspace = await workspaceExists(cwd);
350
- if (!hadWorkspace) {
351
- if (showOnly) {
352
- if (opts.json) {
353
- console.log(JSON.stringify({ preferred_mode: null, workspace: "none", cloud_authenticated: await isCloudAuthenticated() }));
354
- }
355
- else {
356
- console.log("No workspace. Run `cognetivy init` or `cognetivy mode` to create one and set mode.");
357
- }
358
- return;
359
- }
360
- if (selectMode) {
361
- if (selectMode === "cloud") {
362
- await ensureMinimalWorkspace(cwd);
363
- }
364
- else {
365
- await ensureWorkspace(cwd, { force: false });
366
- }
367
- const indexAfter = await readWorkflowIndexOptional(cwd);
368
- await writeWorkflowIndex({ ...(indexAfter ?? { current_workflow_id: "", workflows: [] }), preferred_mode: selectMode }, cwd);
369
- console.log(`Default mode set to ${selectMode === "cloud" ? "Cloud" : "Local"}.`);
370
- return;
371
- }
372
- // Interactive with no workspace: do not create yet; create only after user chooses below
373
- }
374
- const index = await readWorkflowIndexOptional(cwd);
375
- const preferredMode = index?.preferred_mode ?? null;
376
- const cloudAuthenticated = await isCloudAuthenticated();
377
- const minimal = await isWorkspaceMinimal(cwd);
378
- if (showOnly) {
379
- if (opts.json) {
380
- console.log(JSON.stringify({
381
- preferred_mode: preferredMode,
382
- workspace: minimal ? "minimal" : "full",
383
- cloud_authenticated: cloudAuthenticated,
384
- }));
385
- }
386
- else {
387
- const modeLabel = preferredMode === "local" ? "Local" : preferredMode === "cloud" ? "Cloud" : isCloudMode() ? "Cloud (API key set)" : "Local (no API key)";
388
- console.log(`Preferred mode: ${preferredMode ?? "not set"}`);
389
- console.log(`Workspace: ${minimal ? "minimal (cloud-only)" : "full"}`);
390
- console.log(`Cloud authenticated: ${cloudAuthenticated ? "yes" : "no"}`);
391
- console.log(`Effective default: ${modeLabel}`);
392
- }
393
- return;
394
- }
395
- if (selectMode) {
396
- const base = index ?? { current_workflow_id: "wf_default", workflows: [] };
397
- await writeWorkflowIndex({ ...base, preferred_mode: selectMode }, cwd);
398
- if (selectMode === "local" && minimal) {
399
- await ensureWorkspace(cwd, { force: false });
400
- }
401
- console.log(`Default mode set to ${selectMode === "cloud" ? "Cloud" : "Local"}.`);
402
- return;
403
- }
404
- if (!process.stdin.isTTY) {
405
- console.error("Interactive terminal required. Use `cognetivy mode --show`, `cognetivy mode --json`, or `cognetivy mode --select <local|cloud>`.");
406
- process.exit(1);
407
- }
408
- const currentLabel = preferredMode === "local"
409
- ? "Local"
410
- : preferredMode === "cloud"
411
- ? cloudAuthenticated
412
- ? "Cloud (signed in)"
413
- : "Cloud (preferred, not signed in)"
414
- : isCloudMode()
415
- ? "Cloud (API key set)"
416
- : "Local";
417
- p.intro("cognetivy mode");
418
- p.note(hadWorkspace ? `Current: ${currentLabel}. Workspace: ${minimal ? "minimal" : "full"}.` : "No workspace yet. Choose mode to create one.", "Current state");
419
- p.note("Local: data stays in .cognetivy/ on this machine—you own it, view in Studio here.\nCloud: sign in once; view run status from anywhere (web or mobile browser) and work from anywhere.", "Local vs Cloud");
420
- const choice = await p.select({
421
- message: "Local or Cloud?",
422
- options: [
423
- { value: "local", label: "Local", hint: "Data in .cognetivy/ here; view in Studio on this machine" },
424
- { value: "cloud", label: "Cloud", hint: "View run status from anywhere (web or mobile browser)" },
425
- ],
426
- });
427
- if (p.isCancel(choice)) {
428
- p.cancel("Cancelled.");
429
- process.exit(0);
430
- }
431
- const selected = choice;
432
- if (!hadWorkspace) {
433
- if (selected === "cloud") {
434
- await ensureMinimalWorkspace(cwd);
435
- }
436
- else {
437
- await ensureWorkspace(cwd, { force: false });
438
- }
439
- }
440
- const indexToWrite = await readWorkflowIndexOptional(cwd);
441
- await writeWorkflowIndex({ ...(indexToWrite ?? { current_workflow_id: "", workflows: [] }), preferred_mode: selected }, cwd);
442
- if (selected === "local" && (await isWorkspaceMinimal(cwd))) {
443
- await ensureWorkspace(cwd, { force: false });
444
- p.note("Full local workspace created (default workflow added).", "Local mode");
445
- }
446
- if (selected === "cloud" && !cloudAuthenticated) {
447
- p.note("Run `cognetivy auth login` to sign in to the cloud.", "Tip");
448
- }
449
- p.outro(`Default mode set to ${selected === "cloud" ? "Cloud" : "Local"}.`);
344
+ const appUrl = getCloudAppUrl();
345
+ await openUrl(appUrl);
346
+ printOpenedUrlMessage(appUrl, { workflow: false });
450
347
  });
451
348
  const workflowCmd = program
452
349
  .command("workflow")
@@ -463,61 +360,34 @@ function filterWorkflowsByQuery(items, q) {
463
360
  }
464
361
  workflowCmd
465
362
  .command("list")
466
- .description("List workflows (id, name, description only). From cloud when authenticated, else local .cognetivy. Add --q to filter by name/description.")
363
+ .description("List workflows (id, name, description only) from cloud. Add --q to filter by name/description.")
467
364
  .option("--q <query>", "Filter by name or description (search)")
468
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
469
- .option("--local", "Use local .cognetivy workspace only")
470
365
  .action(async (opts) => {
471
- const useCloud = await resolveUseCloud(process.cwd(), opts);
472
- if (useCloud) {
473
- const orgId = await resolveCloudOrganizationId();
474
- const list = await cloudListWorkflows(orgId, opts.q);
475
- const out = list.map((w) => ({ id: w.id, name: w.name, description: w.description ?? undefined }));
476
- console.log(JSON.stringify(out, null, 2));
477
- return;
478
- }
479
- const cwd = process.cwd();
480
- await requireWorkspace(cwd);
481
- const workflows = await listWorkflows(cwd);
482
- let out = workflows.map((w) => ({ id: w.workflow_id, name: w.name, description: w.description }));
483
- if (opts.q)
484
- out = filterWorkflowsByQuery(out, opts.q);
366
+ await requireCloudApiKey();
367
+ const orgId = await resolveCloudOrganizationId();
368
+ const list = await cloudListWorkflows(orgId, opts.q);
369
+ const out = list.map((w) => ({ id: w.id, name: w.name, description: w.description ?? undefined }));
485
370
  console.log(JSON.stringify(out, null, 2));
486
371
  });
487
372
  workflowCmd
488
373
  .command("search")
489
374
  .description("Search workflows by name or description (id, name, description only). Use only when the user asks to list or search workflows.")
490
375
  .option("--q <query>", "Search term (optional; omit to list all)")
491
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
492
- .option("--local", "Use local .cognetivy workspace only")
493
376
  .action(async (opts) => {
494
- const useCloud = await resolveUseCloud(process.cwd(), opts);
495
- if (useCloud) {
496
- const orgId = await resolveCloudOrganizationId();
497
- const list = await cloudListWorkflows(orgId, opts.q);
498
- const out = list.map((w) => ({ id: w.id, name: w.name, description: w.description ?? undefined }));
499
- console.log(JSON.stringify(out, null, 2));
500
- return;
501
- }
502
- const cwd = process.cwd();
503
- await requireWorkspace(cwd);
504
- const workflows = await listWorkflows(cwd);
505
- let out = workflows.map((w) => ({ id: w.workflow_id, name: w.name, description: w.description }));
506
- if (opts.q)
507
- out = filterWorkflowsByQuery(out, opts.q);
377
+ await requireCloudApiKey();
378
+ const orgId = await resolveCloudOrganizationId();
379
+ const list = await cloudListWorkflows(orgId, opts.q);
380
+ const out = list.map((w) => ({ id: w.id, name: w.name, description: w.description ?? undefined }));
508
381
  console.log(JSON.stringify(out, null, 2));
509
382
  });
510
383
  workflowCmd
511
384
  .command("create")
512
- .description("Create a new workflow. Use --name for an empty workflow; --file <path> or stdin (omit both --name and --file, pipe payload) for one-call create with name/description/nodes/kinds.")
385
+ .description("Create a new workflow in cloud. Use --name for an empty workflow; --file <path> or stdin for one-call create with name/description/nodes/kinds.")
513
386
  .option("--name <string>", "Workflow name (required if no --file and not reading from stdin; overrides file/stdin name if both)")
514
387
  .option("--file <path>", "Path to JSON with name, description?, nodes?, kinds?; omit to read from stdin when --name not set)")
515
- .option("--id <string>", "Workflow id (local only; default: generated)")
516
388
  .option("--description <string>", "Workflow description (overrides file/stdin if both)")
517
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
518
- .option("--local", "Use local .cognetivy workspace only")
519
389
  .action(async (opts) => {
520
- const useCloud = await resolveUseCloud(process.cwd(), opts);
390
+ await requireCloudApiKey();
521
391
  const cwd = process.cwd();
522
392
  let raw;
523
393
  if (opts.file) {
@@ -545,60 +415,21 @@ workflowCmd
545
415
  process.exit(1);
546
416
  }
547
417
  }
548
- if (useCloud) {
549
- const orgId = await resolveCloudOrganizationId();
550
- const result = await cloudCreateWorkflowFull({
551
- organizationId: orgId,
552
- name,
553
- description,
554
- nodes: nodes.length > 0 ? nodes : undefined,
555
- kinds,
556
- });
557
- console.log(result.id);
558
- if (result.versionId) {
559
- console.error(`Version: ${result.versionId}`);
560
- }
561
- await ensureWorkspace(cwd);
562
- const index = await readWorkflowIndex(cwd);
563
- await writeWorkflowIndex({ ...index, cloud_current_workflow_id: result.id }, cwd);
564
- return;
565
- }
566
- await requireWorkspace(cwd);
567
- const id = opts.id ?? generateId("wf");
568
- const now = new Date().toISOString();
569
- const wf = {
570
- workflow_id: id,
418
+ const orgId = await resolveCloudOrganizationId();
419
+ const result = await cloudCreateWorkflowFull({
420
+ organizationId: orgId,
571
421
  name,
572
422
  description,
573
- current_version_id: "v1",
574
- created_at: now,
575
- };
576
- await writeWorkflowRecord(wf, cwd);
577
- await writeWorkflowVersionRecord({
578
- workflow_id: id,
579
- version_id: "v1",
580
- name: "v1",
581
- created_at: now,
582
- nodes: nodes,
583
- }, cwd);
584
- if (kinds && Object.keys(kinds).length > 0) {
585
- const merged = { workflow_id: id, kinds: {} };
586
- for (const [k, v] of Object.entries(kinds)) {
587
- merged.kinds[k] = mergeKindTemplate(k, v);
588
- }
589
- await writeCollectionSchema(id, merged, cwd);
590
- }
591
- else {
592
- const { createDefaultCollectionSchema } = await import("./default-collection-schema.js");
593
- await writeCollectionSchema(id, createDefaultCollectionSchema(id), cwd);
423
+ nodes: nodes.length > 0 ? nodes : undefined,
424
+ kinds,
425
+ });
426
+ console.log(result.id);
427
+ if (result.versionId) {
428
+ console.error(`Version: ${result.versionId}`);
594
429
  }
430
+ await ensureMinimalWorkspace(cwd);
595
431
  const index = await readWorkflowIndex(cwd);
596
- const next = {
597
- ...index,
598
- workflows: [...(index.workflows ?? []), { workflow_id: id, name: wf.name, description: wf.description, current_version_id: wf.current_version_id }],
599
- };
600
- await writeWorkflowIndex(next, cwd);
601
- console.log(id);
432
+ await writeWorkflowIndex({ ...index, cloud_current_workflow_id: result.id }, cwd);
602
433
  return;
603
434
  }
604
435
  const name = opts.name;
@@ -606,153 +437,88 @@ workflowCmd
606
437
  console.error("Error: --name <string> is required when not using --file or stdin.");
607
438
  process.exit(1);
608
439
  }
609
- if (useCloud) {
610
- const orgId = await resolveCloudOrganizationId();
611
- const workflow = await cloudCreateWorkflow({
612
- organizationId: orgId,
613
- name,
614
- description: opts.description,
615
- });
616
- await cloudCreateWorkflowVersion(workflow.id, []);
617
- console.log(workflow.id);
618
- return;
619
- }
620
- await requireWorkspace(cwd);
621
- const id = opts.id ?? generateId("wf");
622
- const now = new Date().toISOString();
623
- const wf = {
624
- workflow_id: id,
440
+ const orgId = await resolveCloudOrganizationId();
441
+ const workflow = await cloudCreateWorkflow({
442
+ organizationId: orgId,
625
443
  name,
626
444
  description: opts.description,
627
- current_version_id: "v1",
628
- created_at: now,
629
- };
630
- await writeWorkflowRecord(wf, cwd);
631
- await writeWorkflowVersionRecord({
632
- workflow_id: id,
633
- version_id: "v1",
634
- name: "v1",
635
- created_at: now,
636
- nodes: [],
637
- }, cwd);
638
- const { createDefaultCollectionSchema } = await import("./default-collection-schema.js");
639
- await writeCollectionSchema(id, createDefaultCollectionSchema(id), cwd);
640
- const index = await readWorkflowIndex(cwd);
641
- const next = {
642
- ...index,
643
- workflows: [...(index.workflows ?? []), { workflow_id: id, name: wf.name, description: wf.description, current_version_id: wf.current_version_id }],
644
- };
645
- await writeWorkflowIndex(next, cwd);
646
- console.log(id);
445
+ });
446
+ await cloudCreateWorkflowVersion(workflow.id, []);
447
+ console.log(workflow.id);
647
448
  });
648
449
  workflowCmd
649
450
  .command("select")
650
- .description("Select current workflow (updates workflows/index.json). Use --workflow <id>; add --cloud to set default for cloud. For agents, prefer passing --workflow explicitly on each command.")
451
+ .description("Set default cloud workflow id in .cognetivy/workflows/index.json. Workflow must exist on the server.")
651
452
  .requiredOption("--workflow <workflow_id>", "Workflow ID")
652
- .option("--cloud", "Set as default workflow for cloud (persisted in index; workflow must exist on server)")
653
- .option("--local", "Select from local workspace only (default if neither --cloud nor --local)")
654
453
  .action(async (opts) => {
454
+ await requireCloudApiKey();
655
455
  const cwd = process.cwd();
656
- const useCloud = opts.cloud === true;
657
- if (useCloud) {
658
- try {
659
- await cloudGetWorkflow(opts.workflow);
660
- }
661
- catch {
662
- console.error(`Error: workflow "${opts.workflow}" not found on server.`);
663
- process.exit(1);
664
- }
665
- await ensureWorkspace(cwd);
666
- const index = await readWorkflowIndex(cwd);
667
- await writeWorkflowIndex({ ...index, cloud_current_workflow_id: opts.workflow }, cwd);
668
- console.log(opts.workflow);
669
- return;
456
+ try {
457
+ await cloudGetWorkflow(opts.workflow);
670
458
  }
671
- await requireWorkspace(cwd);
672
- const index = await readWorkflowIndex(cwd);
673
- if (!(index.workflows ?? []).some((w) => w.workflow_id === opts.workflow)) {
674
- console.error(`Error: workflow "${opts.workflow}" not found.`);
459
+ catch {
460
+ console.error(`Error: workflow "${opts.workflow}" not found on server.`);
675
461
  process.exit(1);
676
462
  }
677
- await writeWorkflowIndex({ ...index, current_workflow_id: opts.workflow }, cwd);
463
+ await ensureMinimalWorkspace(cwd);
464
+ const index = await readWorkflowIndex(cwd);
465
+ await writeWorkflowIndex({ ...index, cloud_current_workflow_id: opts.workflow }, cwd);
678
466
  console.log(opts.workflow);
679
467
  });
680
468
  workflowCmd
681
469
  .command("get")
682
470
  .description("Print a workflow version (nodes, etc.). Default: --workflow/--version omitted uses current workflow and latest version. Use --output-format yaml for fewer tokens.")
683
- .option("--workflow <workflow_id>", "Workflow ID (default: current from workflows/index.json)")
684
- .option("--version <version_id>", "Version ID (default: latest; cloud uses version uuid)")
471
+ .option("--workflow <workflow_id>", "Workflow ID (default: cloud_current_workflow_id or COGNETIVY_WORKFLOW_ID)")
472
+ .option("--version <version_id>", "Version ID (default: latest)")
685
473
  .option("--output-format <format>", "Output format: json or yaml", "json")
686
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
687
- .option("--local", "Use local .cognetivy workspace only")
688
474
  .action(async (opts) => {
475
+ await requireCloudApiKey();
689
476
  const cwd = process.cwd();
690
- const useCloud = await resolveUseCloud(process.cwd(), opts);
691
- if (useCloud) {
692
- const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
693
- if (!workflowId) {
694
- console.error("Error: In cloud mode --workflow <id>, COGNETIVY_WORKFLOW_ID, or run `cognetivy workflow select --workflow <id> --cloud` is required.");
695
- process.exit(1);
696
- }
697
- let versionId = opts.version;
477
+ const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
478
+ if (!workflowId) {
479
+ console.error("Error: --workflow <id>, COGNETIVY_WORKFLOW_ID, or `cognetivy workflow select --workflow <id>` is required.");
480
+ process.exit(1);
481
+ }
482
+ let versionId = opts.version;
483
+ if (!versionId) {
484
+ const versions = await cloudGetWorkflowVersions(workflowId);
485
+ versionId = versions[0]?.id;
698
486
  if (!versionId) {
699
- const versions = await cloudGetWorkflowVersions(workflowId);
700
- versionId = versions[0]?.id;
701
- if (!versionId) {
702
- console.error("No versions found for workflow.");
703
- process.exit(1);
704
- }
487
+ console.error("No versions found for workflow.");
488
+ process.exit(1);
705
489
  }
706
- const version = await cloudGetWorkflowVersion(workflowId, versionId);
707
- const outFormat = opts.outputFormat === "yaml" ? "yaml" : "json";
708
- console.log(stringifyPayload(version, outFormat));
709
- return;
710
490
  }
711
- await requireWorkspace(cwd);
712
- const index = await readWorkflowIndex(cwd);
713
- const workflowId = opts.workflow ?? index.current_workflow_id;
714
- const wf = await readWorkflowRecord(workflowId, cwd);
715
- const versionId = opts.version ?? wf.current_version_id;
716
- const version = await readWorkflowVersionRecord(workflowId, versionId, cwd);
491
+ const version = await cloudGetWorkflowVersion(workflowId, versionId);
717
492
  const outFormat = opts.outputFormat === "yaml" ? "yaml" : "json";
718
493
  console.log(stringifyPayload(version, outFormat));
719
494
  });
720
495
  workflowCmd
721
496
  .command("versions")
722
- .description("List versions for a workflow. Default: --workflow omitted uses current workflow (index or COGNETIVY_WORKFLOW_ID). Output: version ids (and metadata in cloud).")
497
+ .description("List versions for a workflow. Default: --workflow omitted uses current workflow (index or COGNETIVY_WORKFLOW_ID).")
723
498
  .option("--workflow <workflow_id>", "Workflow ID (default: current or COGNETIVY_WORKFLOW_ID)")
724
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
725
- .option("--local", "Use local .cognetivy workspace only")
726
499
  .action(async (opts) => {
500
+ await requireCloudApiKey();
727
501
  const cwd = process.cwd();
728
- const useCloud = await resolveUseCloud(process.cwd(), opts);
729
- if (useCloud) {
730
- const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
731
- if (!workflowId) {
732
- console.error("Error: In cloud mode --workflow <id>, COGNETIVY_WORKFLOW_ID, or run `cognetivy workflow select --workflow <id> --cloud` is required.");
733
- process.exit(1);
734
- }
735
- const versions = await cloudGetWorkflowVersions(workflowId);
736
- console.log(JSON.stringify(versions, null, 2));
737
- return;
502
+ const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
503
+ if (!workflowId) {
504
+ console.error("Error: --workflow <id>, COGNETIVY_WORKFLOW_ID, or `cognetivy workflow select --workflow <id>` is required.");
505
+ process.exit(1);
738
506
  }
739
- await requireWorkspace(cwd);
740
- const index = await readWorkflowIndex(cwd);
741
- const workflowId = opts.workflow ?? index.current_workflow_id;
742
- const ids = await listWorkflowVersionIds(workflowId, cwd);
743
- console.log(JSON.stringify(ids, null, 2));
507
+ const versions = await cloudGetWorkflowVersions(workflowId);
508
+ console.log(JSON.stringify(versions, null, 2));
744
509
  });
745
510
  workflowCmd
746
511
  .command("templates")
747
- .description("List or pick workflow templates. With TTY: interactive picker then apply. Use --list to print templates as JSON (no picker).")
512
+ .description("List or pick workflow templates. With TTY: interactive picker then create workflow in cloud. Use --list to print templates as JSON (no picker).")
748
513
  .option("--list", "Print templates JSON instead of interactive picker")
749
514
  .action(async (opts) => {
750
515
  const cwd = process.cwd();
751
- await requireWorkspace(cwd);
516
+ await ensureMinimalWorkspace(cwd);
752
517
  if (opts.list || !process.stdin.isTTY) {
753
518
  console.log(JSON.stringify(listWorkflowTemplates(), null, 2));
754
519
  return;
755
520
  }
521
+ await requireCloudApiKey();
756
522
  const templates = listWorkflowTemplatesForPicker();
757
523
  const picked = await p.select({
758
524
  message: "Pick a workflow template",
@@ -767,16 +533,18 @@ workflowCmd
767
533
  process.exit(0);
768
534
  }
769
535
  const templateId = picked;
770
- const result = await applyWorkflowTemplateToWorkspace({ cwd, templateId });
771
- p.note(`Applied template \"${result.template.name}\"\nWorkflow: ${result.workflow.workflow_id}\nNow current: ${result.workflow.workflow_id}`, "Template applied");
536
+ const orgId = await resolveCloudOrganizationId();
537
+ const result = await applyWorkflowTemplateToCloud({ organizationId: orgId, templateId, cwd });
538
+ p.note(`Created workflow "${result.template.name}" (${result.workflowId}) in cloud.`, "Template applied");
772
539
  console.log(JSON.stringify({
773
540
  template_id: result.template.id,
774
- workflow_id: result.workflow.workflow_id,
775
- current_workflow_id: result.workflow.workflow_id,
776
- version_id: result.version.version_id,
541
+ workflow_id: result.workflowId,
542
+ cloud_current_workflow_id: result.workflowId,
543
+ version_id: result.versionId,
777
544
  }, null, 2));
778
- const studioUrl = `http://127.0.0.1:${STUDIO_DEFAULT_PORT}`;
779
- openUrl(studioUrl).catch(() => { });
545
+ const appUrl = getCloudAppUrl();
546
+ await openUrl(appUrl);
547
+ printOpenedUrlMessage(appUrl, { workflow: true });
780
548
  });
781
549
  workflowCmd
782
550
  .command("template")
@@ -792,14 +560,14 @@ workflowCmd
792
560
  });
793
561
  workflowCmd
794
562
  .command("apply-template")
795
- .description("Apply a built-in template: creates a new workflow and sets it current. Use --id to skip picker; omit for interactive template choice.")
563
+ .description("Create a workflow in cloud from a built-in template. Use --id to skip picker; omit for interactive template choice.")
796
564
  .option("--id <template_id>", "Template ID (omit for interactive picker)")
797
- .option("--workflow <workflow_id>", "Workflow ID to create (default: wf_<template_id>)")
798
565
  .option("--name <string>", "Optional workflow name override")
799
566
  .option("--description <string>", "Optional workflow description override")
800
567
  .action(async (opts) => {
568
+ await requireCloudApiKey();
801
569
  const cwd = process.cwd();
802
- await requireWorkspace(cwd);
570
+ await ensureMinimalWorkspace(cwd);
803
571
  let templateId = opts.id;
804
572
  if (!templateId) {
805
573
  if (!process.stdin.isTTY) {
@@ -822,18 +590,19 @@ workflowCmd
822
590
  templateId = picked;
823
591
  }
824
592
  try {
825
- const result = await applyWorkflowTemplateToWorkspace({
826
- cwd,
593
+ const orgId = await resolveCloudOrganizationId();
594
+ const result = await applyWorkflowTemplateToCloud({
595
+ organizationId: orgId,
827
596
  templateId,
828
- workflowId: opts.workflow,
597
+ cwd,
829
598
  workflowName: opts.name,
830
599
  workflowDescription: opts.description,
831
600
  });
832
601
  console.log(JSON.stringify({
833
602
  template_id: result.template.id,
834
- workflow_id: result.workflow.workflow_id,
835
- current_workflow_id: result.workflow.workflow_id,
836
- version_id: result.version.version_id,
603
+ workflow_id: result.workflowId,
604
+ cloud_current_workflow_id: result.workflowId,
605
+ version_id: result.versionId,
837
606
  }, null, 2));
838
607
  }
839
608
  catch (err) {
@@ -843,80 +612,40 @@ workflowCmd
843
612
  });
844
613
  workflowCmd
845
614
  .command("set")
846
- .description("Set workflow version from file or stdin (creates new version). Use --file <path> or omit to read from stdin. Default --workflow uses current. Cloud when authenticated.")
615
+ .description("Set workflow version from file or stdin (creates new version in cloud). Use --file <path> or omit to read from stdin.")
847
616
  .option("--file <path>", "Path to workflow JSON file (must contain 'nodes' array); omit to read from stdin")
848
617
  .option("--workflow <workflow_id>", "Workflow ID (default: current or COGNETIVY_WORKFLOW_ID)")
849
- .option("--name <string>", "Optional version name (local only)")
850
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
851
- .option("--local", "Use local .cognetivy workspace only")
852
618
  .action(async (opts) => {
619
+ await requireCloudApiKey();
853
620
  const cwd = process.cwd();
854
621
  const raw = opts.file
855
622
  ? await fs.readFile(path.resolve(cwd, opts.file), "utf-8")
856
623
  : await readPayloadFromFileOrStdin(undefined, cwd);
857
624
  const format = opts.file ? formatFromFilePath(opts.file) : "auto";
858
625
  const data = parsePayload(raw, format);
859
- const useCloud = await resolveUseCloud(process.cwd(), opts);
860
- if (useCloud) {
861
- const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
862
- if (!workflowId) {
863
- console.error("Error: In cloud mode --workflow <id>, COGNETIVY_WORKFLOW_ID, or run `cognetivy workflow select --workflow <id> --cloud` is required.");
864
- process.exit(1);
865
- }
866
- const nodes = Array.isArray(data?.nodes) ? data.nodes : [];
867
- const version = await cloudCreateWorkflowVersion(workflowId, nodes);
868
- console.log(version.id);
869
- return;
870
- }
871
- await requireWorkspace(cwd);
872
- const index = await readWorkflowIndex(cwd);
873
- const workflowId = opts.workflow ?? index.current_workflow_id;
874
- const wf = await readWorkflowRecord(workflowId, cwd);
875
- const existing = await listWorkflowVersionIds(workflowId, cwd);
876
- const nums = existing.map((v) => parseInt(v.replace(/^v/, ""), 10)).filter((n) => !Number.isNaN(n));
877
- const nextNum = Math.max(0, ...nums) + 1;
878
- const newVersionId = `v${nextNum}`;
879
- const dataRecord = data;
880
- const version = {
881
- ...dataRecord,
882
- workflow_id: workflowId,
883
- version_id: newVersionId,
884
- name: opts.name,
885
- created_at: new Date().toISOString(),
886
- nodes: Array.isArray(dataRecord.nodes) ? dataRecord.nodes : [],
887
- };
888
- validateWorkflowVersion(version);
889
- // Measure change remaining-1: require collection schema presence when setting a version with nodes.
890
- const referencedKinds = getCollectionNamesFromNodes(version.nodes ?? []);
891
- if (referencedKinds.length > 0) {
892
- const schema = await readCollectionSchema(workflowId, cwd);
893
- const missing = referencedKinds.filter((k) => k !== "run_input" && (schema.kinds?.[k]?.item_schema == null));
894
- if (missing.length > 0) {
895
- console.error(`Error: Collection schema (kinds) is required for all collections referenced in nodes. Missing kinds for: ${missing.join(", ")}. Add a "kinds" entry for each kind before setting this workflow version.`);
896
- process.exit(1);
897
- }
626
+ const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
627
+ if (!workflowId) {
628
+ console.error("Error: --workflow <id>, COGNETIVY_WORKFLOW_ID, or `cognetivy workflow select --workflow <id>` is required.");
629
+ process.exit(1);
898
630
  }
899
- await writeWorkflowVersionRecord(version, cwd);
900
- await writeWorkflowRecord({ ...wf, current_version_id: newVersionId }, cwd);
901
- const workflows = (index.workflows ?? []).map((w) => w.workflow_id === workflowId ? { ...w, current_version_id: newVersionId } : w);
902
- await writeWorkflowIndex({ ...index, workflows }, cwd);
903
- console.log(newVersionId);
631
+ const nodes = Array.isArray(data?.nodes) ? data.nodes : [];
632
+ const version = await cloudCreateWorkflowVersion(workflowId, nodes);
633
+ console.log(version.id);
904
634
  });
905
635
  const runCmd = program
906
636
  .command("run")
907
637
  .description("Run lifecycle: start, status, step, complete. Run with no subcommand to see all. Every response includes COGNETIVY_NEXT_STEP when a node is in progress.");
908
638
  runCmd
909
639
  .command("start")
910
- .description("Start a new run. Requires --input <path>, --input -, or --input-inline <json>; and --name. Prints run_id and COGNETIVY_NEXT_STEP. Default --workflow/--version use current.")
640
+ .description("Start a new run (cloud). Requires --input <path>, --input -, or --input-inline <json>; and --name. Prints run_id and COGNETIVY_NEXT_STEP.")
911
641
  .option("--input <path>", "Path to JSON file with run input, or '-' to read from stdin")
912
642
  .option("--input-inline <json>", "Run input as JSON string (alternative to --input; no file or stdin needed)")
913
643
  .option("--name <string>", "Human-readable name for the run (e.g. 'Q1 ideas exploration')")
914
644
  .option("--by <string>", "Actor (e.g. agent:cursor); defaults to config or 'cli'")
915
- .option("--workflow <workflow_id>", "Workflow ID (default: current from workflows/index.json)")
916
- .option("--version <version_id>", "Workflow version ID (default: workflow.current_version_id)")
917
- .option("--cloud", "Use Cognetivy cloud API (default when COGNETIVY_API_KEY is set; see `cognetivy auth status`)")
918
- .option("--local", "Use local .cognetivy workspace (overrides API key)")
645
+ .option("--workflow <workflow_id>", "Workflow ID (default: cloud_current_workflow_id or COGNETIVY_WORKFLOW_ID)")
646
+ .option("--version <version_id>", "Workflow version ID (default: latest on server)")
919
647
  .action(async (opts) => {
648
+ await requireCloudApiKey();
920
649
  const cwd = process.cwd();
921
650
  if (!opts.input && !opts.inputInline) {
922
651
  console.error("Error: Provide --input <path>, --input -, or --input-inline <json> for run input.");
@@ -943,488 +672,145 @@ runCmd
943
672
  }
944
673
  }
945
674
  const input = parsePayload(inputRaw, "auto");
946
- const useCloud = await resolveUseCloud(process.cwd(), opts);
947
- if (useCloud) {
948
- const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
949
- if (!workflowId) {
950
- console.error("Error: In cloud mode --workflow <id>, COGNETIVY_WORKFLOW_ID, or run `cognetivy workflow select --workflow <id> --cloud` is required.");
951
- process.exit(1);
952
- }
953
- try {
954
- const result = await cloudCreateRun({
955
- workflowId,
956
- workflowVersionId: opts.version,
957
- name: opts.name,
958
- input,
959
- });
960
- console.log(result.run_id);
961
- console.log(`COGNETIVY_RUN_ID=${result.run_id}`);
962
- const next = result.next_step;
963
- const action = mapCloudActionToLocal(next.action);
964
- console.log(formatNextStepLine(result.run_id, "RUNNING", { ...next, action }, result.current_node_id, result.current_node_ids));
965
- }
966
- catch (err) {
967
- console.error(err instanceof Error ? err.message : String(err));
968
- process.exit(1);
969
- }
970
- return;
675
+ const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
676
+ if (!workflowId) {
677
+ console.error("Error: --workflow <id>, COGNETIVY_WORKFLOW_ID, or `cognetivy workflow select --workflow <id>` is required.");
678
+ process.exit(1);
679
+ }
680
+ try {
681
+ const result = await cloudCreateRun({
682
+ workflowId,
683
+ workflowVersionId: opts.version,
684
+ name: opts.name,
685
+ input,
686
+ });
687
+ console.log(result.run_id);
688
+ console.log(`COGNETIVY_RUN_ID=${result.run_id}`);
689
+ const next = result.next_step;
690
+ const action = mapCloudActionToCliAction(next.action);
691
+ console.log(formatNextStepLine(result.run_id, "RUNNING", { ...next, action }, result.current_node_id, result.current_node_ids));
692
+ }
693
+ catch (err) {
694
+ console.error(err instanceof Error ? err.message : String(err));
695
+ process.exit(1);
971
696
  }
972
- await requireWorkspace(cwd);
973
- const index = await readWorkflowIndex(cwd);
974
- const workflowId = opts.workflow ?? index.current_workflow_id;
975
- const wf = await readWorkflowRecord(workflowId, cwd);
976
- const versionId = opts.version ?? wf.current_version_id;
977
- const runId = generateId("run");
978
- const by = opts.by ?? (await resolveBy(cwd));
979
- const now = new Date().toISOString();
980
- const runRecord = {
981
- run_id: runId,
982
- ...(opts.name && { name: opts.name }),
983
- workflow_id: workflowId,
984
- workflow_version_id: versionId,
985
- status: "running",
986
- input,
987
- created_at: now,
988
- };
989
- await writeRunFile(runRecord, cwd);
990
- const event = {
991
- ts: now,
992
- type: "run_started",
993
- by,
994
- data: { workflow_id: workflowId, workflow_version_id: versionId, input },
995
- };
996
- await appendEventLine(runId, event, cwd);
997
- // Seed run_input collection item for collection→node flow.
998
- const systemNodeId = "__system__";
999
- const systemNodeResultId = generateId("node_result");
1000
- const nodeResult = {
1001
- node_result_id: systemNodeResultId,
1002
- run_id: runId,
1003
- workflow_id: workflowId,
1004
- workflow_version_id: versionId,
1005
- node_id: systemNodeId,
1006
- status: NodeResultStatus.Completed,
1007
- started_at: now,
1008
- completed_at: now,
1009
- output: JSON.stringify(input, null, 2),
1010
- writes: [{ kind: "run_input", item_ids: ["run_input"] }],
1011
- };
1012
- await writeNodeResult(runId, systemNodeId, nodeResult, cwd);
1013
- const runInputPayload = typeof input.name === "string" && input.name !== ""
1014
- ? input
1015
- : { name: "Run input", ...input };
1016
- await appendCollection(runId, "run_input", runInputPayload, { id: "run_input", created_by_node_id: systemNodeId, created_by_node_result_id: systemNodeResultId }, cwd);
1017
- console.log(runId);
1018
- console.log(`COGNETIVY_RUN_ID=${runId}`);
1019
- let { next_step, current_node_id, current_node_ids } = await getNextStep(runId, cwd);
1020
- if (next_step.action === "run_node" && next_step.node_id) {
1021
- await appendEventLine(runId, { ts: now, type: "step_started", by, data: { step: next_step.node_id, step_id: next_step.node_id } }, cwd);
1022
- await writeNodeResult(runId, next_step.node_id, {
1023
- node_result_id: generateId("node_result"),
1024
- run_id: runId,
1025
- workflow_id: workflowId,
1026
- workflow_version_id: versionId,
1027
- node_id: next_step.node_id,
1028
- status: NodeResultStatus.Started,
1029
- started_at: now,
1030
- }, cwd);
1031
- const after = await getNextStep(runId, cwd);
1032
- next_step = after.next_step;
1033
- current_node_id = after.current_node_id;
1034
- current_node_ids = after.current_node_ids;
1035
- }
1036
- console.log(formatNextStepLine(runId, "running", next_step, current_node_id, current_node_ids));
1037
697
  });
1038
698
  runCmd
1039
699
  .command("complete")
1040
700
  .description("Mark a run as completed (appends run_completed and persists status). Requires --run <id>. Run this after all nodes are done.")
1041
701
  .requiredOption("--run <run_id>", "Run ID to mark complete")
1042
702
  .action(async (opts) => {
703
+ await requireCloudApiKey();
1043
704
  const cwd = process.cwd();
1044
- const useCloud = isCloudMode();
1045
- if (useCloud) {
1046
- try {
1047
- await cloudAppendEvents(opts.run, {
1048
- events: [{ type: "run_completed", by: await resolveBy(cwd), data: {} }],
1049
- });
1050
- const run = await cloudGetRun(opts.run);
1051
- console.log(`Run "${opts.run}" marked as completed.`);
1052
- console.log(formatNextStepLine(opts.run, run.status, { action: "done", hint: "Run finished." }));
1053
- }
1054
- catch (err) {
1055
- console.error(err instanceof Error ? err.message : String(err));
1056
- process.exit(1);
1057
- }
1058
- return;
705
+ try {
706
+ await cloudAppendEvents(opts.run, {
707
+ events: [{ type: "run_completed", by: await resolveBy(cwd), data: {} }],
708
+ });
709
+ const run = await cloudGetRun(opts.run);
710
+ console.log(`Run "${opts.run}" marked as completed.`);
711
+ console.log(formatNextStepLine(opts.run, run.status, { action: "done", hint: "Run finished." }));
1059
712
  }
1060
- const exists = await runExists(opts.run, cwd);
1061
- if (!exists) {
1062
- console.error(`Error: Run "${opts.run}" not found.`);
713
+ catch (err) {
714
+ console.error(err instanceof Error ? err.message : String(err));
1063
715
  process.exit(1);
1064
716
  }
1065
- const by = await resolveBy(cwd);
1066
- const now = new Date().toISOString();
1067
- await appendEventLine(opts.run, { ts: now, type: "run_completed", by, data: {} }, cwd);
1068
- await updateRunFile(opts.run, { status: "completed" }, cwd);
1069
- console.log(`Run "${opts.run}" marked as completed.`);
1070
- console.log(formatNextStepLine(opts.run, "completed", { action: "done", hint: "Run finished." }));
1071
717
  });
1072
718
  runCmd
1073
719
  .command("status")
1074
- .description("Show run metadata, node completion status, and collection counts. Requires --run <id>. Use --json for machine-readable output including next_step.")
720
+ .description("Show run metadata and next_step from cloud. Requires --run <id>. Use --json for machine-readable output.")
1075
721
  .requiredOption("--run <run_id>", "Run ID")
1076
722
  .option("--json", "Output as JSON")
1077
- .option("--cloud", "Use Cognetivy cloud API (default when COGNETIVY_API_KEY is set; see `cognetivy auth status`)")
1078
- .option("--local", "Use local .cognetivy workspace (overrides API key)")
1079
723
  .action(async (opts) => {
1080
- const useCloud = await resolveUseCloud(process.cwd(), opts);
1081
- if (useCloud) {
1082
- try {
1083
- const [run, nextData] = await Promise.all([cloudGetRun(opts.run), cloudGetNext(opts.run)]);
1084
- const next = nextData.next_step;
1085
- const action = mapCloudActionToLocal(next.action);
1086
- const next_step = { ...next, action };
1087
- if (opts.json) {
1088
- console.log(JSON.stringify({ run: { id: run.id, status: run.status, workflowId: run.workflowId }, next_step, current_node_id: nextData.current_node_id, current_node_ids: nextData.current_node_ids }, null, 2));
1089
- return;
1090
- }
1091
- console.log("Run:", run.id, run.status, `(${run.workflowId})`);
1092
- if (nextData.current_node_ids?.length)
1093
- console.log("Current nodes (in progress):", nextData.current_node_ids.join(", "));
1094
- else if (nextData.current_node_id)
1095
- console.log("Current node (in progress):", nextData.current_node_id);
1096
- console.log(formatNextStepLine(run.id, run.status, next_step, nextData.current_node_id, nextData.current_node_ids));
1097
- }
1098
- catch (err) {
1099
- console.error(err instanceof Error ? err.message : String(err));
1100
- process.exit(1);
1101
- }
1102
- return;
1103
- }
1104
- const cwd = process.cwd();
1105
- const exists = await runExists(opts.run, cwd);
1106
- if (!exists) {
1107
- console.error(`Error: Run "${opts.run}" not found.`);
1108
- process.exit(1);
1109
- }
1110
- const run = await readRunFile(opts.run, cwd);
1111
- let version = null;
724
+ await requireCloudApiKey();
1112
725
  try {
1113
- version = await readWorkflowVersionRecord(run.workflow_id, run.workflow_version_id, cwd);
1114
- }
1115
- catch {
1116
- // workflow version missing
1117
- }
1118
- const nodeResults = await listNodeResults(opts.run, cwd);
1119
- const nodeResultByNodeId = new Map(nodeResults.map((r) => [r.node_id, r]));
1120
- const kinds = await listCollectionKindsForRun(opts.run, cwd);
1121
- const collections = [];
1122
- for (const kind of kinds) {
1123
- const store = await readCollections(opts.run, kind, cwd);
1124
- collections.push({ kind, item_count: store.items.length });
1125
- }
1126
- const nodesList = [];
1127
- if (version?.nodes) {
1128
- for (const node of version.nodes) {
1129
- const nr = nodeResultByNodeId.get(node.id);
1130
- nodesList.push({
1131
- node_id: node.id,
1132
- status: nr?.status ?? "-",
1133
- completed_at: nr?.completed_at,
1134
- });
726
+ const [run, nextData] = await Promise.all([cloudGetRun(opts.run), cloudGetNext(opts.run)]);
727
+ const next = nextData.next_step;
728
+ const action = mapCloudActionToCliAction(next.action);
729
+ const next_step = { ...next, action };
730
+ if (opts.json) {
731
+ console.log(JSON.stringify({
732
+ run: { id: run.id, status: run.status, workflowId: run.workflowId },
733
+ next_step,
734
+ current_node_id: nextData.current_node_id,
735
+ current_node_ids: nextData.current_node_ids,
736
+ }, null, 2));
737
+ return;
1135
738
  }
1136
- }
1137
- // Include __system__ if we have a result for it but it's not in workflow nodes
1138
- if (nodeResultByNodeId.has("__system__") && version?.nodes && !version.nodes.some((n) => n.id === "__system__")) {
1139
- const nr = nodeResultByNodeId.get("__system__");
1140
- nodesList.unshift({ node_id: "__system__", status: nr.status, completed_at: nr.completed_at });
1141
- }
1142
- const { next_step, current_node_id, current_node_ids } = await getNextStep(opts.run, cwd);
1143
- const runSummary = {
1144
- run_id: run.run_id,
1145
- status: run.status,
1146
- name: run.name,
1147
- workflow_id: run.workflow_id,
1148
- workflow_version_id: run.workflow_version_id,
1149
- ...(current_node_id !== undefined && { current_node_id, current_node_status: "in_progress" }),
1150
- ...(current_node_ids !== undefined && current_node_ids.length > 0 && { current_node_ids }),
1151
- };
1152
- if (opts.json) {
1153
- console.log(JSON.stringify({ run: runSummary, nodes: nodesList, collections, next_step, ...(current_node_id !== undefined && { current_node_id }), ...(current_node_ids !== undefined && current_node_ids.length > 0 && { current_node_ids }) }, null, 2));
1154
- return;
1155
- }
1156
- console.log("Run:", run.run_id, run.status, run.name ? `"${run.name}"` : "", `(${run.workflow_id} @ ${run.workflow_version_id})`);
1157
- if (current_node_ids !== undefined && current_node_ids.length > 0) {
1158
- console.log("Current nodes (in progress):", current_node_ids.join(", "));
1159
- }
1160
- else if (current_node_id !== undefined) {
1161
- console.log("Current node (in progress):", current_node_id);
1162
- }
1163
- if (nodesList.length > 0) {
1164
- console.log("Nodes:");
1165
- for (const n of nodesList) {
1166
- const at = n.completed_at ? ` @ ${n.completed_at}` : "";
1167
- console.log(` ${n.node_id}: ${n.status}${at}`);
739
+ console.log("Run:", run.id, run.status, `(${run.workflowId})`);
740
+ if (nextData.current_node_ids?.length) {
741
+ console.log("Current nodes (in progress):", nextData.current_node_ids.join(", "));
1168
742
  }
1169
- }
1170
- else if (!version) {
1171
- console.log("Nodes: (workflow version unavailable)");
1172
- }
1173
- if (collections.length > 0) {
1174
- console.log("Collections:");
1175
- for (const c of collections) {
1176
- console.log(` ${c.kind}: ${c.item_count} item(s)`);
743
+ else if (nextData.current_node_id) {
744
+ console.log("Current node (in progress):", nextData.current_node_id);
1177
745
  }
746
+ console.log(formatNextStepLine(run.id, run.status, next_step, nextData.current_node_id, nextData.current_node_ids));
747
+ }
748
+ catch (err) {
749
+ console.error(err instanceof Error ? err.message : String(err));
750
+ process.exit(1);
1178
751
  }
1179
- console.log(formatNextStepLine(run.run_id, run.status, next_step, current_node_id, current_node_ids));
1180
752
  });
1181
753
  runCmd
1182
754
  .command("step")
1183
- .description("Advance run: run with only --run <id> to start the next node; add --node <id> and --collection-kind with --collection-file <path> (or stdin) to complete a node. Prefer --collection-file to avoid shell prompts in agents. Prints COGNETIVY_NEXT_STEP.")
755
+ .description("Advance run (cloud): run with only --run <id> to start the next node; add --node <id> and --collection-kind with --collection-file <path> (or stdin) to complete a node.")
1184
756
  .requiredOption("--run <run_id>", "Run ID")
1185
757
  .option("--node <node_id>", "Node ID (required when completing a node with payload)")
1186
758
  .option("--collection-kind <kind>", "Collection kind when completing node (payload from --collection-file or stdin)")
1187
- .option("--collection-file <path>", "Path to JSON/YAML payload file (omit to read from stdin; prefer this in agents to avoid heredocs)")
1188
- .option("--collection-mode <mode>", "set (array) or append (single object); default: infer", "infer")
1189
- .option("--by <string>", "Actor; defaults to config or 'cli'")
1190
- .option("--cloud", "Use Cognetivy cloud API (default when COGNETIVY_API_KEY is set; see `cognetivy auth status`)")
1191
- .option("--local", "Use local .cognetivy workspace (overrides API key)")
759
+ .option("--collection-file <path>", "Path to JSON/YAML payload file (omit to read from stdin)")
1192
760
  .action(async (opts) => {
1193
- const useCloud = await resolveUseCloud(process.cwd(), opts);
1194
- if (useCloud) {
1195
- try {
1196
- if (opts.node !== undefined) {
1197
- const body = {};
1198
- if (opts.collectionKind) {
1199
- const raw = await readPayloadFromFileOrStdin(opts.collectionFile, process.cwd());
1200
- body.collectionKind = opts.collectionKind;
1201
- body.collectionPayload = parsePayload(raw, "auto");
1202
- }
1203
- const result = await cloudCompleteNode(opts.run, opts.node, body);
1204
- const next = result.next_step;
1205
- const action = mapCloudActionToLocal(next.action);
1206
- console.log(formatNextStepLine(opts.run, "running", { ...next, action }, result.current_node_id, result.current_node_ids));
1207
- }
1208
- else {
1209
- const nextData = await cloudGetNext(opts.run);
1210
- const next = nextData.next_step;
1211
- const action = mapCloudActionToLocal(next.action);
1212
- if (action === "run_node" && next.node_id) {
1213
- const result = await cloudStartNode(opts.run, next.node_id);
1214
- const rNext = result.next_step;
1215
- const rAction = mapCloudActionToLocal(rNext.action);
1216
- console.log(formatNextStepLine(opts.run, "running", { ...rNext, action: rAction }, result.current_node_id, result.current_node_ids));
1217
- }
1218
- else if (action === "run_nodes_parallel" && next.runnable_node_ids?.length) {
1219
- for (const nodeId of next.runnable_node_ids) {
1220
- await cloudStartNode(opts.run, nodeId);
1221
- }
1222
- const after = await cloudGetNext(opts.run);
1223
- const afterAction = mapCloudActionToLocal(after.next_step.action);
1224
- console.log(formatNextStepLine(opts.run, "running", { ...after.next_step, action: afterAction }, after.current_node_id, after.current_node_ids));
1225
- }
1226
- else {
1227
- console.log(formatNextStepLine(opts.run, "running", { ...next, action }, nextData.current_node_id, nextData.current_node_ids));
1228
- }
1229
- }
1230
- }
1231
- catch (err) {
1232
- console.error(err instanceof Error ? err.message : String(err));
1233
- process.exit(1);
1234
- }
1235
- return;
1236
- }
1237
- const cwd = process.cwd();
1238
- const exists = await runExists(opts.run, cwd);
1239
- if (!exists) {
1240
- console.error(`Error: Run "${opts.run}" not found.`);
1241
- process.exit(1);
1242
- }
1243
- const run = await readRunFile(opts.run, cwd);
1244
- if (run.status !== "running") {
1245
- console.error(`Error: Run is not running (status: ${run.status}).`);
1246
- process.exit(1);
1247
- }
1248
- const by = opts.by ?? (await resolveBy(cwd));
1249
- const now = new Date().toISOString();
1250
- if (opts.node !== undefined) {
1251
- const nodeId = opts.node;
1252
- const existingResult = await readNodeResult(opts.run, nodeId, cwd);
1253
- // Replace semantics (B2): if this node was completed before and we are completing it again
1254
- // with new collection output, delete the previous output items created by that node result.
1255
- let priorItemIds = [];
1256
- if (existingResult?.writes != null && Array.isArray(existingResult.writes)) {
1257
- for (const w of existingResult.writes) {
1258
- const itemIdsMaybe = w && typeof w === "object" ? w.item_ids : undefined;
1259
- if (Array.isArray(itemIdsMaybe)) {
1260
- for (const id of itemIdsMaybe) {
1261
- if (typeof id === "string" && id.trim() !== "")
1262
- priorItemIds.push(id);
1263
- }
1264
- }
761
+ await requireCloudApiKey();
762
+ try {
763
+ if (opts.node !== undefined) {
764
+ const body = {};
765
+ if (opts.collectionKind) {
766
+ const raw = await readPayloadFromFileOrStdin(opts.collectionFile, process.cwd());
767
+ body.collectionKind = opts.collectionKind;
768
+ body.collectionPayload = parsePayload(raw, "auto");
1265
769
  }
770
+ const result = await cloudCompleteNode(opts.run, opts.node, body);
771
+ const next = result.next_step;
772
+ const action = mapCloudActionToCliAction(next.action);
773
+ console.log(formatNextStepLine(opts.run, "running", { ...next, action }, result.current_node_id, result.current_node_ids));
1266
774
  }
1267
- if (!opts.collectionKind && !existingResult) {
1268
- await appendEventLine(opts.run, { ts: now, type: "step_started", by, data: { step: nodeId, step_id: nodeId } }, cwd);
1269
- await writeNodeResult(opts.run, nodeId, {
1270
- node_result_id: generateId("node_result"),
1271
- run_id: opts.run,
1272
- workflow_id: run.workflow_id,
1273
- workflow_version_id: run.workflow_version_id,
1274
- node_id: nodeId,
1275
- status: NodeResultStatus.Started,
1276
- started_at: now,
1277
- }, cwd);
1278
- const { next_step: afterStep, current_node_id: afterCurrent, current_node_ids: afterIds } = await getNextStep(opts.run, cwd);
1279
- const runAfter = await readRunFile(opts.run, cwd);
1280
- console.log(formatNextStepLine(runAfter.run_id, runAfter.status, afterStep, afterCurrent, afterIds));
1281
- return;
1282
- }
1283
- if (!existingResult || existingResult.status !== "started") {
1284
- const startedAt = new Date().toISOString();
1285
- await appendEventLine(opts.run, { ts: startedAt, type: "step_started", by, data: { step: nodeId, step_id: nodeId } }, cwd);
1286
- await writeNodeResult(opts.run, nodeId, {
1287
- node_result_id: generateId("node_result"),
1288
- run_id: opts.run,
1289
- workflow_id: run.workflow_id,
1290
- workflow_version_id: run.workflow_version_id,
1291
- node_id: nodeId,
1292
- status: NodeResultStatus.Started,
1293
- started_at: startedAt,
1294
- }, cwd);
1295
- }
1296
- const nodeResultId = generateId("node_result");
1297
- if (opts.collectionKind) {
1298
- if (priorItemIds.length > 0) {
1299
- await deleteCollectionItemsByIds(opts.run, priorItemIds, cwd);
775
+ else {
776
+ const nextData = await cloudGetNext(opts.run);
777
+ const next = nextData.next_step;
778
+ const action = mapCloudActionToCliAction(next.action);
779
+ if (action === "run_node" && next.node_id) {
780
+ const result = await cloudStartNode(opts.run, next.node_id);
781
+ const rNext = result.next_step;
782
+ const rAction = mapCloudActionToCliAction(rNext.action);
783
+ console.log(formatNextStepLine(opts.run, "running", { ...rNext, action: rAction }, result.current_node_id, result.current_node_ids));
1300
784
  }
1301
- const raw = await readPayloadFromFileOrStdin(opts.collectionFile, cwd);
1302
- const payload = parsePayload(raw, "auto");
1303
- const mode = opts.collectionMode === "set" || opts.collectionMode === "append" ? opts.collectionMode : Array.isArray(payload) ? "set" : "append";
1304
- const writes = [];
1305
- if (mode === "set") {
1306
- const payloads = payload.map((p, i) => ({
1307
- ...p,
1308
- id: p.id ?? `${opts.collectionKind}_${i}`,
1309
- }));
1310
- await writeCollections(opts.run, opts.collectionKind, payloads, { created_by_node_id: nodeId, created_by_node_result_id: nodeResultId }, cwd);
1311
- writes.push({ kind: opts.collectionKind, item_ids: payloads.map((p) => p.id) });
785
+ else if (action === "run_nodes_parallel" && next.runnable_node_ids?.length) {
786
+ for (const nodeId of next.runnable_node_ids) {
787
+ await cloudStartNode(opts.run, nodeId);
788
+ }
789
+ const after = await cloudGetNext(opts.run);
790
+ const afterAction = mapCloudActionToCliAction(after.next_step.action);
791
+ console.log(formatNextStepLine(opts.run, "running", { ...after.next_step, action: afterAction }, after.current_node_id, after.current_node_ids));
1312
792
  }
1313
793
  else {
1314
- const item = await appendCollection(opts.run, opts.collectionKind, payload, { created_by_node_id: nodeId, created_by_node_result_id: nodeResultId }, cwd);
1315
- writes.push({ kind: opts.collectionKind, item_ids: [item.id] });
794
+ console.log(formatNextStepLine(opts.run, "running", { ...next, action }, nextData.current_node_id, nextData.current_node_ids));
1316
795
  }
1317
- const result = {
1318
- node_result_id: nodeResultId,
1319
- run_id: opts.run,
1320
- workflow_id: run.workflow_id,
1321
- workflow_version_id: run.workflow_version_id,
1322
- node_id: nodeId,
1323
- status: NodeResultStatus.Completed,
1324
- started_at: now,
1325
- completed_at: now,
1326
- writes,
1327
- };
1328
- await writeNodeResult(opts.run, nodeId, result, cwd);
1329
- await appendEventLine(opts.run, { ts: now, type: "step_completed", by, data: { step: nodeId, step_id: nodeId } }, cwd);
1330
- }
1331
- else {
1332
- await writeNodeResult(opts.run, nodeId, {
1333
- node_result_id: nodeResultId,
1334
- run_id: opts.run,
1335
- workflow_id: run.workflow_id,
1336
- workflow_version_id: run.workflow_version_id,
1337
- node_id: nodeId,
1338
- status: NodeResultStatus.Completed,
1339
- started_at: now,
1340
- completed_at: now,
1341
- }, cwd);
1342
- await appendEventLine(opts.run, { ts: now, type: "step_completed", by, data: { step: nodeId, step_id: nodeId } }, cwd);
1343
796
  }
1344
797
  }
1345
- else {
1346
- const { next_step: ns } = await getNextStep(opts.run, cwd);
1347
- if (ns.action === "run_nodes_parallel" && ns.runnable_node_ids?.length) {
1348
- for (const nodeId of ns.runnable_node_ids) {
1349
- const existing = await readNodeResult(opts.run, nodeId, cwd);
1350
- if (!existing || existing.status !== NodeResultStatus.Started) {
1351
- await appendEventLine(opts.run, { ts: now, type: "step_started", by, data: { step: nodeId, step_id: nodeId } }, cwd);
1352
- await writeNodeResult(opts.run, nodeId, {
1353
- node_result_id: generateId("node_result"),
1354
- run_id: opts.run,
1355
- workflow_id: run.workflow_id,
1356
- workflow_version_id: run.workflow_version_id,
1357
- node_id: nodeId,
1358
- status: NodeResultStatus.Started,
1359
- started_at: now,
1360
- }, cwd);
1361
- }
1362
- }
1363
- const runAfter = await readRunFile(opts.run, cwd);
1364
- console.log(formatNextStepLine(runAfter.run_id, runAfter.status, ns, undefined, ns.runnable_node_ids));
1365
- return;
1366
- }
1367
- if (ns.action === "run_node" && ns.node_id) {
1368
- const nodeResultId = generateId("node_result");
1369
- await appendEventLine(opts.run, { ts: now, type: "step_started", by, data: { step: ns.node_id, step_id: ns.node_id } }, cwd);
1370
- await writeNodeResult(opts.run, ns.node_id, {
1371
- node_result_id: nodeResultId,
1372
- run_id: opts.run,
1373
- workflow_id: run.workflow_id,
1374
- workflow_version_id: run.workflow_version_id,
1375
- node_id: ns.node_id,
1376
- status: NodeResultStatus.Started,
1377
- started_at: now,
1378
- }, cwd);
1379
- }
1380
- }
1381
- let { next_step, current_node_id, current_node_ids } = await getNextStep(opts.run, cwd);
1382
- if (next_step.action === "run_node" && next_step.node_id) {
1383
- await appendEventLine(opts.run, { ts: now, type: "step_started", by, data: { step: next_step.node_id, step_id: next_step.node_id } }, cwd);
1384
- await writeNodeResult(opts.run, next_step.node_id, {
1385
- node_result_id: generateId("node_result"),
1386
- run_id: opts.run,
1387
- workflow_id: run.workflow_id,
1388
- workflow_version_id: run.workflow_version_id,
1389
- node_id: next_step.node_id,
1390
- status: NodeResultStatus.Started,
1391
- started_at: now,
1392
- }, cwd);
1393
- const after = await getNextStep(opts.run, cwd);
1394
- next_step = after.next_step;
1395
- current_node_id = after.current_node_id;
1396
- current_node_ids = after.current_node_ids;
1397
- }
1398
- const runAfter = await readRunFile(opts.run, cwd);
1399
- console.log(formatNextStepLine(runAfter.run_id, runAfter.status, next_step, current_node_id, current_node_ids));
1400
- });
1401
- runCmd
1402
- .command("set-name")
1403
- .description("Set or update the human-readable name for an existing run. Requires --run <id> and --name <string>.")
1404
- .requiredOption("--run <run_id>", "Run ID")
1405
- .requiredOption("--name <string>", "Name for the run")
1406
- .action(async (opts) => {
1407
- const cwd = process.cwd();
1408
- const exists = await runExists(opts.run, cwd);
1409
- if (!exists) {
1410
- console.error(`Error: Run "${opts.run}" not found.`);
798
+ catch (err) {
799
+ console.error(err instanceof Error ? err.message : String(err));
1411
800
  process.exit(1);
1412
801
  }
1413
- await updateRunFile(opts.run, { name: opts.name }, cwd);
1414
- console.log(`Run "${opts.run}" named "${opts.name}".`);
1415
802
  });
1416
803
  const eventCmd = program
1417
804
  .command("event")
1418
805
  .description("Event log operations (low-level). Run with no subcommand to see: append. Prefer run complete for ending runs.");
1419
806
  eventCmd
1420
807
  .command("append")
1421
- .description("Append one event to run's log. Omit --file to read JSON from stdin. For run_completed, prefer 'cognetivy run complete --run <id>' which does both.")
808
+ .description("Append one event to run's log (cloud). Omit --file to read JSON from stdin. For run_completed, prefer 'cognetivy run complete --run <id>'.")
1422
809
  .requiredOption("--run <run_id>", "Run ID")
1423
810
  .option("--file <path>", "Path to JSON file (omit to read event from stdin)")
1424
811
  .option("--by <string>", "Actor; defaults to config or 'cli'")
1425
- .option("--cloud", "Use Cognetivy cloud API (default when COGNETIVY_API_KEY is set; see `cognetivy auth status`)")
1426
- .option("--local", "Use local .cognetivy workspace (overrides API key)")
1427
812
  .action(async (opts) => {
813
+ await requireCloudApiKey();
1428
814
  const cwd = process.cwd();
1429
815
  const raw = await readPayloadFromFileOrStdin(opts.file, cwd);
1430
816
  const format = opts.file ? formatFromFilePath(opts.file) : "auto";
@@ -1437,57 +823,43 @@ eventCmd
1437
823
  by: data.by ?? by,
1438
824
  data: data.data ?? data,
1439
825
  };
1440
- const useCloud = await resolveUseCloud(process.cwd(), opts);
1441
- if (useCloud) {
1442
- try {
1443
- const result = await cloudAppendEvents(opts.run, {
1444
- events: [{ type: event.type, by: event.by, data: event.data }],
1445
- });
1446
- console.log(`Appended ${result.appended} event(s).`);
1447
- }
1448
- catch (err) {
1449
- console.error(err instanceof Error ? err.message : String(err));
1450
- process.exit(1);
1451
- }
1452
- return;
826
+ try {
827
+ const result = await cloudAppendEvents(opts.run, {
828
+ events: [{ type: event.type, by: event.by, data: event.data }],
829
+ });
830
+ console.log(`Appended ${result.appended} event(s).`);
1453
831
  }
1454
- const exists = await runExists(opts.run, cwd);
1455
- if (!exists) {
1456
- console.error(`Error: Run "${opts.run}" not found. Run \`cognetivy run start\` first.`);
832
+ catch (err) {
833
+ console.error(err instanceof Error ? err.message : String(err));
1457
834
  process.exit(1);
1458
835
  }
1459
- await appendEventLine(opts.run, event, cwd);
1460
- if (event.type === "run_completed") {
1461
- await updateRunFile(opts.run, { status: "completed" }, cwd);
1462
- }
1463
- console.log("Appended event.");
1464
836
  });
1465
837
  const collectionSchemaCmd = program
1466
838
  .command("collection-schema")
1467
839
  .description("Collection schema (workflow-scoped; kinds and item_schema). Used when creating/editing workflows; run get/set with --workflow.");
1468
840
  collectionSchemaCmd
1469
841
  .command("get")
1470
- .description("Print collection schema JSON for a workflow. Use --run to resolve workflow from a run; --kind to print only one kind.")
1471
- .option("--workflow <workflow_id>", "Workflow ID (default: current from workflows/index.json)")
842
+ .description("Print collection schema JSON for a workflow (cloud). Use --run to resolve workflow from a run; --kind to print only one kind.")
843
+ .option("--workflow <workflow_id>", "Workflow ID (default: cloud_current_workflow_id or COGNETIVY_WORKFLOW_ID)")
1472
844
  .option("--run <run_id>", "Run ID (resolve workflow from this run; overrides --workflow)")
1473
845
  .option("--kind <kind>", "Print only this collection kind's schema")
1474
846
  .action(async (opts) => {
847
+ await requireCloudApiKey();
1475
848
  const cwd = process.cwd();
1476
- await requireWorkspace(cwd);
1477
849
  let workflowId;
1478
850
  if (opts.run) {
1479
- const run = await readRunFile(opts.run, cwd);
1480
- workflowId = run.workflow_id;
851
+ const run = await cloudGetRun(opts.run);
852
+ workflowId = run.workflowId;
1481
853
  }
1482
854
  else {
1483
- const index = await readWorkflowIndex(cwd);
1484
- workflowId = opts.workflow ?? index.current_workflow_id ?? "";
855
+ const resolved = await resolveCloudWorkflowId(cwd, opts.workflow);
856
+ workflowId = resolved ?? "";
1485
857
  }
1486
858
  if (!workflowId) {
1487
- console.error("Error: specify --workflow <id>, --run <run_id>, or set current workflow (workflows/index.json).");
859
+ console.error("Error: specify --workflow <id>, --run <run_id>, or set default with workflow select.");
1488
860
  process.exit(1);
1489
861
  }
1490
- const schema = await readCollectionSchema(workflowId, cwd);
862
+ const schema = await cloudGetCollectionSchema(workflowId);
1491
863
  if (opts.kind) {
1492
864
  const kindSchema = schema.kinds?.[opts.kind];
1493
865
  if (kindSchema == null) {
@@ -1502,12 +874,11 @@ collectionSchemaCmd
1502
874
  });
1503
875
  collectionSchemaCmd
1504
876
  .command("set")
1505
- .description("Set collection schema from JSON file. Requires --file <path>. Default --workflow uses current. Cloud when authenticated.")
877
+ .description("Set collection schema from JSON file (cloud). Requires --file <path>.")
1506
878
  .requiredOption("--file <path>", "Path to collection-schema JSON file")
1507
- .option("--workflow <workflow_id>", "Workflow ID (default: current from workflows/index.json)")
1508
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
1509
- .option("--local", "Use local .cognetivy workspace only")
879
+ .option("--workflow <workflow_id>", "Workflow ID (default: current cloud workflow)")
1510
880
  .action(async (opts) => {
881
+ await requireCloudApiKey();
1511
882
  const cwd = process.cwd();
1512
883
  const raw = await fs.readFile(path.resolve(cwd, opts.file), "utf-8");
1513
884
  const schema = parsePayload(raw, formatFromFilePath(opts.file));
@@ -1515,340 +886,160 @@ collectionSchemaCmd
1515
886
  console.error("Error: schema must have a 'kinds' object.");
1516
887
  process.exit(1);
1517
888
  }
1518
- const useCloud = await resolveUseCloud(cwd, opts);
1519
- if (useCloud) {
1520
- const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
1521
- if (!workflowId) {
1522
- console.error("Error: In cloud mode --workflow <id>, COGNETIVY_WORKFLOW_ID, or run `cognetivy workflow select --workflow <id> --cloud` is required.");
1523
- process.exit(1);
1524
- }
1525
- const kinds = {};
1526
- for (const [k, v] of Object.entries(schema.kinds)) {
1527
- const merged = mergeKindTemplate(k, v);
1528
- kinds[k] = { name: merged.name, description: merged.description, item_schema: merged.item_schema };
1529
- }
1530
- await cloudSetCollectionSchema(workflowId, kinds);
1531
- console.log("Collection schema updated.");
1532
- return;
889
+ const workflowId = await resolveCloudWorkflowId(cwd, opts.workflow);
890
+ if (!workflowId) {
891
+ console.error("Error: --workflow <id>, COGNETIVY_WORKFLOW_ID, or `cognetivy workflow select --workflow <id>` is required.");
892
+ process.exit(1);
1533
893
  }
1534
- await requireWorkspace(cwd);
1535
- const index = await readWorkflowIndex(cwd);
1536
- const workflowId = opts.workflow ?? index.current_workflow_id;
1537
- const merged = { workflow_id: workflowId, kinds: {} };
894
+ const kinds = {};
1538
895
  for (const [k, v] of Object.entries(schema.kinds)) {
1539
- merged.kinds[k] = mergeKindTemplate(k, v);
896
+ const merged = mergeKindTemplate(k, v);
897
+ kinds[k] = { name: merged.name, description: merged.description, item_schema: merged.item_schema };
1540
898
  }
1541
- await writeCollectionSchema(workflowId, merged, cwd);
899
+ await cloudSetCollectionSchema(workflowId, kinds);
1542
900
  console.log("Collection schema updated.");
1543
901
  });
1544
902
  const collectionCmd = program
1545
903
  .command("collection")
1546
- .description("Structured collections per run (schema-backed). Run with no subcommand to see: list, get, set, append. Used by run step when completing nodes.");
904
+ .description("Structured collections per run (cloud). Subcommands: list, get.");
1547
905
  collectionCmd
1548
906
  .command("list")
1549
- .description("List collection kinds that have data for a run. Requires --run <id>. Cloud when authenticated.")
907
+ .description("List collection kinds that have data for a run. Requires --run <id>.")
1550
908
  .requiredOption("--run <run_id>", "Run ID")
1551
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
1552
- .option("--local", "Use local .cognetivy workspace only")
1553
909
  .action(async (opts) => {
1554
- const cwd = process.cwd();
1555
- const useCloud = await resolveUseCloud(process.cwd(), opts);
1556
- if (useCloud) {
1557
- try {
1558
- const result = await cloudListCollectionKinds(opts.run);
1559
- console.log(JSON.stringify(result.kinds, null, 2));
1560
- }
1561
- catch (err) {
1562
- console.error(err instanceof Error ? err.message : String(err));
1563
- process.exit(1);
1564
- }
1565
- return;
910
+ await requireCloudApiKey();
911
+ try {
912
+ const result = await cloudListCollectionKinds(opts.run);
913
+ console.log(JSON.stringify(result.kinds, null, 2));
914
+ }
915
+ catch (err) {
916
+ console.error(err instanceof Error ? err.message : String(err));
917
+ process.exit(1);
1566
918
  }
1567
- const kinds = await listCollectionKindsForRun(opts.run, cwd);
1568
- console.log(JSON.stringify(kinds, null, 2));
1569
919
  });
1570
920
  collectionCmd
1571
921
  .command("get")
1572
- .description("Get all items of a collection kind for a run. Requires --run <id> and --kind or --collection (e.g. run_input, sources).")
922
+ .description("Get all items of a collection kind for a run. Requires --run <id> and --kind or --collection.")
1573
923
  .requiredOption("--run <run_id>", "Run ID")
1574
- .option("--kind <kind>", "Collection kind (e.g. sources, ideas, run_input)")
924
+ .option("--kind <kind>", "Collection kind (e.g. sources, run_input)")
1575
925
  .option("--collection <kind>", "Collection kind (alias for --kind)")
1576
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
1577
- .option("--local", "Use local .cognetivy workspace only")
1578
926
  .action(async (opts) => {
927
+ await requireCloudApiKey();
1579
928
  const kind = opts.kind ?? opts.collection;
1580
929
  if (!kind) {
1581
930
  console.error("Error: required option --kind <kind> or --collection <kind> not specified.");
1582
931
  process.exit(1);
1583
932
  }
1584
- const cwd = process.cwd();
1585
- const useCloud = await resolveUseCloud(process.cwd(), opts);
1586
- if (useCloud) {
1587
- try {
1588
- const result = await cloudGetCollectionItems(opts.run, kind);
1589
- console.log(JSON.stringify(result, null, 2));
1590
- }
1591
- catch (err) {
1592
- console.error(err instanceof Error ? err.message : String(err));
1593
- process.exit(1);
1594
- }
1595
- return;
1596
- }
1597
- const store = await readCollections(opts.run, kind, cwd);
1598
- console.log(JSON.stringify(store, null, 2));
1599
- });
1600
- collectionCmd
1601
- .command("set")
1602
- .description("Replace all items of a kind for a run. Requires --run, --kind, --node, --node-result. Omit --file to read from stdin.")
1603
- .requiredOption("--run <run_id>", "Run ID")
1604
- .requiredOption("--kind <kind>", "Collection kind")
1605
- .option("--file <path>", "Path to JSON file (omit to read array from stdin)")
1606
- .requiredOption("--node <node_id>", "Node id that created these items")
1607
- .requiredOption("--node-result <node_result_id>", "Node result id that created these items")
1608
- .action(async (opts) => {
1609
- const cwd = process.cwd();
1610
- const raw = await readPayloadFromFileOrStdin(opts.file, cwd);
1611
- const format = opts.file ? formatFromFilePath(opts.file) : "auto";
1612
- const payloads = parsePayload(raw, format);
1613
- if (!Array.isArray(payloads)) {
1614
- console.error("Error: file must contain a JSON or YAML array of collection items.");
1615
- process.exit(1);
1616
- }
1617
- await writeCollections(opts.run, opts.kind, payloads, { created_by_node_id: opts.node, created_by_node_result_id: opts.nodeResult }, cwd);
1618
- console.log(`Set ${payloads.length} collection(s) for kind "${opts.kind}".`);
1619
- });
1620
- collectionCmd
1621
- .command("append")
1622
- .description("Append one collection item to a run's kind. Requires --run, --kind, --node, --node-result. Omit --file to read from stdin.")
1623
- .requiredOption("--run <run_id>", "Run ID")
1624
- .requiredOption("--kind <kind>", "Collection kind")
1625
- .option("--file <path>", "Path to JSON file (omit to read payload from stdin)")
1626
- .requiredOption("--node <node_id>", "Node id that created this item")
1627
- .requiredOption("--node-result <node_result_id>", "Node result id that created this item")
1628
- .option("--id <string>", "Optional collection id")
1629
- .action(async (opts) => {
1630
- const cwd = process.cwd();
1631
- const raw = await readPayloadFromFileOrStdin(opts.file, cwd);
1632
- const format = opts.file ? formatFromFilePath(opts.file) : "auto";
1633
- const payload = parsePayload(raw, format);
1634
- const item = await appendCollection(opts.run, opts.kind, payload, { id: opts.id, created_by_node_id: opts.node, created_by_node_result_id: opts.nodeResult }, cwd);
1635
- console.log(JSON.stringify(item, null, 2));
1636
- });
1637
- const nodeResultCmd = program
1638
- .command("node-result")
1639
- .description("Node results per run (stored snapshots of node outputs). Low-level; prefer run step / node complete for agent flow.");
1640
- nodeResultCmd
1641
- .command("list")
1642
- .description("List node results for a run. Requires --run <id>.")
1643
- .requiredOption("--run <run_id>", "Run ID")
1644
- .action(async (opts) => {
1645
- const cwd = process.cwd();
1646
- const results = await listNodeResults(opts.run, cwd);
1647
- console.log(JSON.stringify(results, null, 2));
1648
- });
1649
- nodeResultCmd
1650
- .command("get")
1651
- .description("Get node result for a node in a run. Requires --run <id> and --node <node_id>.")
1652
- .requiredOption("--run <run_id>", "Run ID")
1653
- .requiredOption("--node <node_id>", "Node ID")
1654
- .action(async (opts) => {
1655
- const cwd = process.cwd();
1656
- const result = await readNodeResult(opts.run, opts.node, cwd);
1657
- if (!result) {
1658
- console.error("Not found.");
1659
- process.exit(1);
933
+ try {
934
+ const result = await cloudGetCollectionItems(opts.run, kind);
935
+ console.log(JSON.stringify(result, null, 2));
1660
936
  }
1661
- console.log(JSON.stringify(result, null, 2));
1662
- });
1663
- nodeResultCmd
1664
- .command("set")
1665
- .description("Create or replace a node result for a node in a run. Requires --run, --node, --status; optional --output/--output-file.")
1666
- .requiredOption("--run <run_id>", "Run ID")
1667
- .requiredOption("--node <node_id>", "Node ID")
1668
- .requiredOption("--status <status>", "Status: started|completed|failed|needs_human")
1669
- .option("--id <node_result_id>", "Node result id (default: generated)")
1670
- .option("--output-file <path>", "Path to a text/markdown file for output")
1671
- .option("--output <string>", "Inline output text")
1672
- .action(async (opts) => {
1673
- const cwd = process.cwd();
1674
- const run = await readRunFile(opts.run, cwd);
1675
- const now = new Date().toISOString();
1676
- const status = opts.status;
1677
- const validStatuses = Object.values(NodeResultStatus);
1678
- if (!validStatuses.includes(status)) {
1679
- console.error(`Error: status must be one of: ${validStatuses.join(", ")}`);
937
+ catch (err) {
938
+ console.error(err instanceof Error ? err.message : String(err));
1680
939
  process.exit(1);
1681
940
  }
1682
- let output = opts.output;
1683
- if (opts.outputFile) {
1684
- output = await fs.readFile(path.resolve(cwd, opts.outputFile), "utf-8");
1685
- }
1686
- const id = opts.id ?? generateId("node_result");
1687
- const completed_at = status === NodeResultStatus.Completed || status === NodeResultStatus.Failed || status === NodeResultStatus.NeedsHuman
1688
- ? now
1689
- : undefined;
1690
- const result = {
1691
- node_result_id: id,
1692
- run_id: opts.run,
1693
- workflow_id: run.workflow_id,
1694
- workflow_version_id: run.workflow_version_id,
1695
- node_id: opts.node,
1696
- status,
1697
- started_at: now,
1698
- completed_at,
1699
- ...(output ? { output } : {}),
1700
- };
1701
- await writeNodeResult(opts.run, opts.node, result, cwd);
1702
- console.log(`COGNETIVY_NODE_RESULT_ID=${id}`);
1703
- console.log(JSON.stringify(result, null, 2));
1704
941
  });
1705
942
  const nodeCmd = program
1706
943
  .command("node")
1707
- .description("Node lifecycle: start (step_started + node result id) and complete (result + optional collection + step_completed). Prefer run step for advancing runs.");
944
+ .description("Node lifecycle (cloud): start a node or complete it with optional collection payload. Prefer `cognetivy run step` when available.");
1708
945
  nodeCmd
1709
946
  .command("start")
1710
- .description("Mark a node as started (append step_started, create node result). Requires --run and --node. Cloud: use when COGNETIVY_API_KEY is set.")
947
+ .description("Mark a node as started on the server. Requires COGNETIVY_API_KEY, --run, and --node.")
1711
948
  .requiredOption("--run <run_id>", "Run ID")
1712
949
  .requiredOption("--node <node_id>", "Workflow node ID")
1713
- .option("--by <string>", "Actor; defaults to config or 'cli'")
1714
- .option("--cloud", "Use Cognetivy cloud API (default when API key is set)")
1715
- .option("--local", "Use local .cognetivy workspace only")
1716
- .action(async (opts) => {
1717
- const useCloud = await resolveUseCloud(process.cwd(), opts);
1718
- if (useCloud) {
1719
- try {
1720
- await cloudStartNode(opts.run, opts.node);
1721
- console.log(`Node "${opts.node}" started.`);
1722
- }
1723
- catch (err) {
1724
- console.error(err instanceof Error ? err.message : String(err));
1725
- process.exit(1);
1726
- }
1727
- return;
950
+ .action(async function nodeStartAction(opts) {
951
+ await requireCloudApiKey();
952
+ try {
953
+ await cloudStartNode(opts.run, opts.node);
954
+ console.log(`Node "${opts.node}" started.`);
1728
955
  }
1729
- const cwd = process.cwd();
1730
- const exists = await runExists(opts.run, cwd);
1731
- if (!exists) {
1732
- console.error(`Error: Run "${opts.run}" not found.`);
956
+ catch (err) {
957
+ console.error(err instanceof Error ? err.message : String(err));
1733
958
  process.exit(1);
1734
959
  }
1735
- const run = await readRunFile(opts.run, cwd);
1736
- const by = opts.by ?? (await resolveBy(cwd));
1737
- const now = new Date().toISOString();
1738
- const nodeResultId = generateId("node_result");
1739
- const event = {
1740
- ts: now,
1741
- type: "step_started",
1742
- by,
1743
- data: { step: opts.node, step_id: opts.node },
1744
- };
1745
- await appendEventLine(opts.run, event, cwd);
1746
- const result = {
1747
- node_result_id: nodeResultId,
1748
- run_id: opts.run,
1749
- workflow_id: run.workflow_id,
1750
- workflow_version_id: run.workflow_version_id,
1751
- node_id: opts.node,
1752
- status: NodeResultStatus.Started,
1753
- started_at: now,
1754
- };
1755
- await writeNodeResult(opts.run, opts.node, result, cwd);
1756
- console.log(`COGNETIVY_NODE_RESULT_ID=${nodeResultId}`);
1757
960
  });
1758
961
  nodeCmd
1759
962
  .command("complete")
1760
- .description("Complete a node: create result, optionally write collection (--collection-kind; omit --collection-file to read from stdin), append step_completed. Requires --run, --node, --status.")
963
+ .description("Complete a node via the cloud API (--status completed only). Optional --output/--output-file, --collection-kind + payload (file or stdin), or --writes-file for multi-output nodes.")
1761
964
  .requiredOption("--run <run_id>", "Run ID")
1762
965
  .requiredOption("--node <node_id>", "Workflow node ID")
1763
- .requiredOption("--status <status>", "Status: completed|failed|needs_human")
966
+ .requiredOption("--status <status>", "Must be: completed (cloud API completes successfully)")
1764
967
  .option("--output <string>", "Inline output text for the node result")
1765
968
  .option("--output-file <path>", "Path to file for node result output")
1766
- .option("--collection-kind <kind>", "Collection kind to set or append (payload from --collection-file or stdin)")
969
+ .option("--collection-kind <kind>", "Collection kind (payload from --collection-file or stdin)")
1767
970
  .option("--collection-file <path>", "Path to JSON payload (omit to read from stdin when --collection-kind is set)")
1768
971
  .option("--collection-mode <mode>", "set (array) or append (single object); default: infer from payload", "infer")
1769
- .option("--by <string>", "Actor; defaults to config or 'cli'")
1770
- .action(async (opts) => {
1771
- const cwd = process.cwd();
1772
- const exists = await runExists(opts.run, cwd);
1773
- if (!exists) {
1774
- console.error(`Error: Run "${opts.run}" not found.`);
972
+ .option("--writes-file <path>", "JSON file: writes [{ kind, item_ids }] for nodes with multiple output kinds")
973
+ .action(async function nodeCompleteAction(opts) {
974
+ await requireCloudApiKey();
975
+ if (opts.status !== "completed") {
976
+ console.error('Error: cloud `node complete` only supports --status completed. For failed or needs_human, use the Cognetivy app or API.');
1775
977
  process.exit(1);
1776
978
  }
1777
- const run = await readRunFile(opts.run, cwd);
1778
- const validStatuses = ["completed", "failed", "needs_human"];
1779
- if (!validStatuses.includes(opts.status)) {
1780
- console.error(`Error: status must be one of: ${validStatuses.join(", ")}`);
979
+ const cwd = process.cwd();
980
+ if (opts.writesFile && opts.collectionKind) {
981
+ console.error("Error: use either --writes-file or --collection-kind, not both.");
1781
982
  process.exit(1);
1782
983
  }
1783
- const status = opts.status;
1784
- const by = opts.by ?? (await resolveBy(cwd));
1785
- const now = new Date().toISOString();
1786
984
  let output = opts.output;
1787
985
  if (opts.outputFile) {
1788
986
  output = await fs.readFile(path.resolve(cwd, opts.outputFile), "utf-8");
1789
987
  }
1790
- const nodeResultId = generateId("node_result");
1791
- const writes = [];
1792
- if (opts.collectionKind) {
988
+ const body = {};
989
+ if (output !== undefined) {
990
+ body.output = output;
991
+ }
992
+ if (opts.writesFile) {
993
+ const raw = await fs.readFile(path.resolve(cwd, opts.writesFile), "utf-8");
994
+ body.writes = parsePayload(raw, formatFromFilePath(opts.writesFile));
995
+ }
996
+ else if (opts.collectionKind) {
1793
997
  const raw = await readPayloadFromFileOrStdin(opts.collectionFile, cwd);
1794
998
  const payload = parsePayload(raw, opts.collectionFile ? formatFromFilePath(opts.collectionFile) : "auto");
1795
- const mode = opts.collectionMode === "set" || opts.collectionMode === "append" ? opts.collectionMode : Array.isArray(payload) ? "set" : "append";
999
+ const mode = opts.collectionMode === "set" || opts.collectionMode === "append"
1000
+ ? opts.collectionMode
1001
+ : Array.isArray(payload)
1002
+ ? "set"
1003
+ : "append";
1004
+ body.collectionKind = opts.collectionKind;
1796
1005
  if (mode === "set") {
1797
- const payloads = payload;
1798
- if (!Array.isArray(payloads)) {
1006
+ if (!Array.isArray(payload)) {
1799
1007
  console.error("Error: collection payload must be a JSON array when using set.");
1800
1008
  process.exit(1);
1801
1009
  }
1802
- const payloadsWithIds = payloads.map((p, i) => ({
1803
- ...p,
1804
- id: p.id ?? `${opts.collectionKind}_${i}`,
1805
- }));
1806
- const itemIds = payloadsWithIds.map((p) => p.id);
1807
- await writeCollections(opts.run, opts.collectionKind, payloadsWithIds, { created_by_node_id: opts.node, created_by_node_result_id: nodeResultId }, cwd);
1808
- writes.push({ kind: opts.collectionKind, item_ids: itemIds });
1010
+ body.collectionPayload = payload;
1809
1011
  }
1810
1012
  else {
1811
- const single = payload;
1812
- const item = await appendCollection(opts.run, opts.collectionKind, single, { created_by_node_id: opts.node, created_by_node_result_id: nodeResultId }, cwd);
1813
- writes.push({ kind: opts.collectionKind, item_ids: [item.id] });
1013
+ if (Array.isArray(payload)) {
1014
+ console.error("Error: collection payload must be a single JSON object when using append.");
1015
+ process.exit(1);
1016
+ }
1017
+ body.collectionPayload = payload;
1814
1018
  }
1815
1019
  }
1816
- const result = {
1817
- node_result_id: nodeResultId,
1818
- run_id: opts.run,
1819
- workflow_id: run.workflow_id,
1820
- workflow_version_id: run.workflow_version_id,
1821
- node_id: opts.node,
1822
- status,
1823
- started_at: now,
1824
- completed_at: now,
1825
- ...(output ? { output } : {}),
1826
- ...(writes.length > 0 ? { writes } : {}),
1827
- };
1828
- await writeNodeResult(opts.run, opts.node, result, cwd);
1829
- const stepCompletedEvent = {
1830
- ts: now,
1831
- type: "step_completed",
1832
- by,
1833
- data: { step: opts.node, step_id: opts.node },
1834
- };
1835
- await appendEventLine(opts.run, stepCompletedEvent, cwd);
1836
- console.log(`COGNETIVY_NODE_RESULT_ID=${nodeResultId}`);
1020
+ try {
1021
+ await cloudCompleteNode(opts.run, opts.node, body);
1022
+ console.log(`Node "${opts.node}" completed.`);
1023
+ }
1024
+ catch (err) {
1025
+ console.error(err instanceof Error ? err.message : String(err));
1026
+ process.exit(1);
1027
+ }
1837
1028
  });
1838
1029
  program
1839
1030
  .command("templates")
1840
- .description("List workflow templates (--list for JSON) or interactively pick one to install and set as current workflow. Run with no options in TTY for picker.")
1031
+ .description("List workflow templates (--list for JSON) or interactively pick one to create in cloud and set as current (requires COGNETIVY_API_KEY).")
1841
1032
  .option("--list", "Print templates as JSON (no picker)")
1842
- .action(async (opts) => {
1033
+ .action(async function templatesCommandAction(opts) {
1843
1034
  const cwd = process.cwd();
1844
1035
  if (opts.list || !process.stdin.isTTY) {
1845
1036
  console.log(JSON.stringify(listWorkflowTemplates(), null, 2));
1846
1037
  return;
1847
1038
  }
1848
- await requireWorkspace(cwd);
1039
+ await requireCloudApiKey();
1849
1040
  const templates = listWorkflowTemplatesForPicker();
1850
1041
  const picked = await p.select({
1851
- message: "Pick a workflow template to install and set as current",
1042
+ message: "Pick a workflow template to create in your cloud org",
1852
1043
  options: templates.map((t) => ({
1853
1044
  value: t.id,
1854
1045
  label: t.name,
@@ -1861,16 +1052,19 @@ program
1861
1052
  }
1862
1053
  const templateId = picked;
1863
1054
  try {
1864
- const result = await applyWorkflowTemplateToWorkspace({ cwd, templateId });
1865
- p.note(`Applied template \"${result.template.name}\"\nWorkflow: ${result.workflow.workflow_id}\nNow current: ${result.workflow.workflow_id}`, "Template applied");
1055
+ const orgId = await resolveCloudOrganizationId();
1056
+ const result = await applyWorkflowTemplateToCloud({ organizationId: orgId, templateId, cwd });
1057
+ p.note(`Created workflow "${result.template.name}" (${result.workflowId}) in cloud.`, "Template applied");
1866
1058
  console.log(JSON.stringify({
1867
1059
  template_id: result.template.id,
1868
- workflow_id: result.workflow.workflow_id,
1869
- current_workflow_id: result.workflow.workflow_id,
1870
- version_id: result.version.version_id,
1060
+ workflow_id: result.workflowId,
1061
+ cloud_current_workflow_id: result.workflowId,
1062
+ version_id: result.versionId,
1871
1063
  }, null, 2));
1872
- const studioUrl = `http://127.0.0.1:${STUDIO_DEFAULT_PORT}`;
1873
- openUrl(studioUrl).catch(() => { });
1064
+ const appUrl = getCloudAppUrl();
1065
+ const url = buildCloudOnboardingUrl(appUrl, result.workflowId);
1066
+ await openUrl(url);
1067
+ printOpenedUrlMessage(url, { workflow: true });
1874
1068
  }
1875
1069
  catch (err) {
1876
1070
  console.error(err instanceof Error ? `Error: ${err.message}` : String(err));
@@ -1883,7 +1077,6 @@ program
1883
1077
  .option("--force", "Overwrite if skill already exists")
1884
1078
  .option("--no-init", "Skip cognetivy workspace init; only install skills")
1885
1079
  .option("--interactive", "Show interactive prompt to choose tool(s) and install accordingly")
1886
- .option("--cloud", "Install cloud-mode skill (no .cognetivy/ workspace; use --cloud on commands)")
1887
1080
  .action(async (target, opts) => {
1888
1081
  const cwd = process.cwd();
1889
1082
  const useTUI = opts.interactive === true || target === undefined;
@@ -1892,9 +1085,8 @@ program
1892
1085
  await runInstallTUI({ cwd, force: opts.force, init: opts.init !== false });
1893
1086
  return;
1894
1087
  }
1895
- const skillMode = opts.cloud ? "cloud" : "local";
1896
- if (opts.init !== false && skillMode === "local") {
1897
- await ensureWorkspace(cwd);
1088
+ if (opts.init !== false) {
1089
+ await ensureMinimalWorkspace(cwd);
1898
1090
  }
1899
1091
  const normalized = target.toLowerCase();
1900
1092
  const targetMap = {
@@ -1919,9 +1111,6 @@ program
1919
1111
  let targetsToInstall = resolved === "all"
1920
1112
  ? ["agent", "agents", "cursor", "factory", "gemini", "openclaw", "opencode", "qwen", "workspace"]
1921
1113
  : [resolved];
1922
- if (skillMode === "cloud") {
1923
- targetsToInstall = targetsToInstall.filter((t) => t !== "workspace");
1924
- }
1925
1114
  const optsCommon = { force: opts.force, cwd, config: skillsConfig };
1926
1115
  try {
1927
1116
  for (const internalTarget of targetsToInstall) {
@@ -1932,7 +1121,7 @@ program
1932
1121
  }
1933
1122
  }
1934
1123
  for (const internalTarget of targetsToInstall) {
1935
- const cognetivyPath = await installCognetivySkill(internalTarget, cwd, skillsConfig, skillMode);
1124
+ const cognetivyPath = await installCognetivySkill(internalTarget, cwd, skillsConfig);
1936
1125
  const label = targetToLabel(internalTarget);
1937
1126
  console.log(`[${label}] Cognetivy skill at ${cognetivyPath}`);
1938
1127
  }
@@ -2207,30 +1396,7 @@ program
2207
1396
  const workspacePath = opts.workspace ? path.resolve(process.cwd(), opts.workspace) : process.cwd();
2208
1397
  await runMcpServer(workspacePath);
2209
1398
  });
2210
- program
2211
- .command("studio")
2212
- .description("Open read-only Studio (workflow, runs, events, collections) in browser. Optional --workspace, --port, --api-only.")
2213
- .option("--workspace <path>", "Workspace directory (default: cwd)")
2214
- .option("--port <number>", "Port for Studio server", (v) => parseInt(v, 10), STUDIO_DEFAULT_PORT)
2215
- .option("--api-only", "Only serve API (for use with Vite dev server; see studio/README)")
2216
- .action(async (opts) => {
2217
- const cwd = process.cwd();
2218
- const workspacePath = opts.workspace ? path.resolve(cwd, opts.workspace) : cwd;
2219
- await requireWorkspace(workspacePath);
2220
- const requestedPort = opts.port ?? STUDIO_DEFAULT_PORT;
2221
- const { port: actualPort } = await startStudioServer(workspacePath, requestedPort, { apiOnly: opts.apiOnly });
2222
- if (!opts.apiOnly) {
2223
- const url = `http://127.0.0.1:${actualPort}`;
2224
- await openUrl(url);
2225
- console.log(`Studio at ${url} (workspace: ${workspacePath}). Press Ctrl+C to stop.`);
2226
- }
2227
- else {
2228
- console.log(`Studio API at http://127.0.0.1:${actualPort} (workspace: ${workspacePath}).`);
2229
- console.log(`Run the app in dev: cd studio && npm run dev, then open http://localhost:5173`);
2230
- console.log("Press Ctrl+C to stop.");
2231
- }
2232
- });
2233
- const DEFAULT_BEHAVIOR_DESCRIPTION = "Guided onboarding: choose cloud or local, sign in if cloud, install or update platform skills (Cursor, Claude Code, etc.), ensure at least one workflow (template picker if needed), then opens the Cognetivy app in your browser.";
1399
+ const DEFAULT_BEHAVIOR_DESCRIPTION = "Guided onboarding: sign in if needed, install or update platform skills (Cursor, Claude Code, etc.), ensure at least one cloud workflow (template picker if needed), then start the local studio (browser + WebSocket executor).";
2234
1400
  program
2235
1401
  .command("docs")
2236
1402
  .description("Open CLI reference (all commands and options) in browser. No arguments.")
@@ -2262,71 +1428,75 @@ function runUpdateNotifier() {
2262
1428
  // ignore
2263
1429
  }
2264
1430
  }
2265
- /** Guided onboarding when user runs `cognetivy` with no args: cloud/local choice, auth, skills, workflow, then open app. */
1431
+ /** Guided onboarding when user runs `cognetivy` with no args: auth, skills, cloud workflow, then open app. */
2266
1432
  async function runDefaultOnboardingFlow(cwd) {
2267
1433
  runUpdateNotifier();
2268
1434
  const authenticated = await isCloudAuthenticated();
2269
- let mode;
1435
+ let loginServerHandle;
2270
1436
  if (!authenticated) {
2271
- p.note("Local: data stays in .cognetivy/ on this machine—you own it, view in Studio here.\nCloud: sign in once; view run status from anywhere (web or mobile browser) and work from anywhere.", "Local vs Cloud");
2272
- const choice = await p.select({
2273
- message: "Local or Cloud?",
2274
- options: [
2275
- { value: "local", label: "Local", hint: "Data in .cognetivy/ here; view in Studio on this machine" },
2276
- { value: "cloud", label: "Cloud", hint: "View run status from anywhere (web or mobile browser)" },
2277
- ],
2278
- });
2279
- if (p.isCancel(choice)) {
2280
- p.cancel("Cancelled.");
2281
- process.exit(0);
1437
+ const signInApiBase = getCloudApiUrl();
1438
+ const apiLooksLocal = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(signInApiBase);
1439
+ p.note(`Sign in once; your API key is stored locally.\nBackend API for this run: ${signInApiBase}${apiLooksLocal ? "" : "\nTip: for a local Nest server use --dev or --api-url http://localhost:3000."}`, "Cognetivy");
1440
+ const staticRoot = resolveLocalStudioStaticRoot();
1441
+ const canAuthViaLocalStudio = localStudioBundleHasCliAuth(staticRoot);
1442
+ if (canAuthViaLocalStudio) {
1443
+ loginServerHandle = await createLocalStudioServer({ workspaceCwd: cwd });
2282
1444
  }
2283
- mode = choice;
2284
- if (mode === "cloud") {
2285
- const appUrl = getCloudAppUrl();
2286
- console.log("Opening browser to sign in…");
2287
- const result = await runLoginFlow({ appUrl });
2288
- if (result.error) {
2289
- console.error(result.error);
2290
- process.exit(1);
1445
+ else {
1446
+ console.log("Tip: Run `npm run build:local-studio` in this package so sign-in opens local studio instead of the hosted app.");
1447
+ }
1448
+ const authAppUrl = loginServerHandle ? resolveLocalStudioBrowserBase(loginServerHandle) : getCloudAppUrl();
1449
+ console.log(loginServerHandle ? "Opening browser to sign in (local studio)…" : "Opening browser to sign in…");
1450
+ const result = await runLoginFlow({ appUrl: authAppUrl });
1451
+ if (result.error) {
1452
+ if (loginServerHandle) {
1453
+ await loginServerHandle.close();
2291
1454
  }
2292
- if (!result.code) {
2293
- console.error("No authorization code received.");
2294
- process.exit(1);
1455
+ console.error(result.error);
1456
+ process.exit(1);
1457
+ }
1458
+ if (!result.code) {
1459
+ if (loginServerHandle) {
1460
+ await loginServerHandle.close();
2295
1461
  }
2296
- const apiUrl = getCloudApiUrl();
2297
- const res = await fetch(`${apiUrl}/auth/cli/token`, {
2298
- method: "POST",
2299
- headers: { "Content-Type": "application/json" },
2300
- body: JSON.stringify({ code: result.code }),
2301
- });
2302
- if (!res.ok) {
2303
- const text = await res.text();
2304
- console.error(text || res.statusText);
2305
- process.exit(1);
1462
+ console.error("No authorization code received.");
1463
+ process.exit(1);
1464
+ }
1465
+ const apiUrl = getCloudApiUrl();
1466
+ const res = await fetch(`${apiUrl}/auth/cli/token`, {
1467
+ method: "POST",
1468
+ headers: { "Content-Type": "application/json" },
1469
+ body: JSON.stringify({ code: result.code }),
1470
+ });
1471
+ if (!res.ok) {
1472
+ const text = await res.text();
1473
+ if (loginServerHandle) {
1474
+ await loginServerHandle.close();
2306
1475
  }
2307
- const data = (await res.json());
2308
- const apiKey = data?.api_key ?? "";
2309
- if (!apiKey) {
2310
- console.error("No API key in response.");
2311
- process.exit(1);
1476
+ console.error(text || res.statusText);
1477
+ process.exit(1);
1478
+ }
1479
+ const data = (await res.json());
1480
+ const apiKey = data?.api_key ?? "";
1481
+ if (!apiKey) {
1482
+ if (loginServerHandle) {
1483
+ await loginServerHandle.close();
2312
1484
  }
2313
- writeStoredApiKey(apiKey);
2314
- console.log("Logged in. API key saved.");
1485
+ console.error("No API key in response.");
1486
+ process.exit(1);
2315
1487
  }
2316
- }
2317
- else {
2318
- const index = await readWorkflowIndexOptional(cwd);
2319
- mode = index?.preferred_mode === "local" ? "local" : "cloud";
1488
+ writeStoredApiKey(apiKey);
1489
+ console.log("Logged in. API key saved.");
2320
1490
  }
2321
1491
  const { runInstallTUI } = await import("./install-tui.js");
2322
1492
  const installedVersion = await readInstalledSkillsVersion(cwd);
2323
1493
  const currentVersion = getCurrentVersionSync();
2324
1494
  if (installedVersion == null) {
2325
- await runInstallTUI({ cwd, init: true, onboardingMode: mode });
1495
+ await runInstallTUI({ cwd, init: true, skipTemplate: true });
2326
1496
  }
2327
1497
  else if (isNewerVersion(currentVersion, installedVersion)) {
2328
1498
  const shouldUpdate = await p.confirm({
2329
- message: `Skills were installed with v${installedVersion}; you're on v${currentVersion}. Update skill files? (Your workflows and runs in .cognetivy are not touched.)`,
1499
+ message: `Skills were installed with v${installedVersion}; you're on v${currentVersion}. Update skill files? (Your .cognetivy skills folder is not removed.)`,
2330
1500
  initialValue: true,
2331
1501
  });
2332
1502
  if (p.isCancel(shouldUpdate)) {
@@ -2336,30 +1506,19 @@ async function runDefaultOnboardingFlow(cwd) {
2336
1506
  await runInstallTUI({ cwd, force: true, init: false });
2337
1507
  }
2338
1508
  }
2339
- if (mode === "local") {
2340
- await ensureWorkspace(cwd, { force: false });
2341
- }
1509
+ await ensureMinimalWorkspace(cwd);
2342
1510
  const index = await readWorkflowIndexOptional(cwd);
2343
- if (index && index.preferred_mode !== mode) {
2344
- await writeWorkflowIndex({ ...index, preferred_mode: mode }, cwd);
2345
- }
2346
1511
  let hasWorkflow = false;
2347
1512
  let cloudWorkflowList = [];
2348
- if (mode === "cloud") {
2349
- try {
2350
- const orgId = await resolveCloudOrganizationId();
2351
- const list = await cloudListWorkflows(orgId);
2352
- cloudWorkflowList = list;
2353
- hasWorkflow = list.length >= 1 || (index?.cloud_current_workflow_id != null);
2354
- }
2355
- catch {
2356
- hasWorkflow = false;
2357
- }
1513
+ try {
1514
+ const orgId = await resolveCloudOrganizationId();
1515
+ const list = await cloudListWorkflows(orgId);
1516
+ cloudWorkflowList = list;
1517
+ hasWorkflow = list.length >= 1 || (index?.cloud_current_workflow_id != null);
2358
1518
  }
2359
- else {
2360
- hasWorkflow = (index?.workflows?.length ?? 0) >= 1;
1519
+ catch {
1520
+ hasWorkflow = false;
2361
1521
  }
2362
- let localCurrentWorkflowId = null;
2363
1522
  let cloudCurrentWorkflowId = null;
2364
1523
  if (!hasWorkflow) {
2365
1524
  const templates = listWorkflowTemplatesForPicker();
@@ -2373,17 +1532,10 @@ async function runDefaultOnboardingFlow(cwd) {
2373
1532
  else {
2374
1533
  const templateId = templateSelection;
2375
1534
  try {
2376
- if (mode === "cloud") {
2377
- const orgId = await resolveCloudOrganizationId();
2378
- const result = await applyWorkflowTemplateToCloud({ organizationId: orgId, templateId, cwd });
2379
- cloudCurrentWorkflowId = result.workflowId;
2380
- p.note(`Created workflow "${result.template.name}" (${result.workflowId}) in cloud.`, "Template applied");
2381
- }
2382
- else {
2383
- const result = await applyWorkflowTemplateToWorkspace({ cwd, templateId });
2384
- localCurrentWorkflowId = result.workflow.workflow_id;
2385
- p.note(`Applied template "${result.template.name}". Workflow: ${result.workflow.workflow_id}`, "Template applied");
2386
- }
1535
+ const orgId = await resolveCloudOrganizationId();
1536
+ const result = await applyWorkflowTemplateToCloud({ organizationId: orgId, templateId, cwd });
1537
+ cloudCurrentWorkflowId = result.workflowId;
1538
+ p.note(`Created workflow "${result.template.name}" (${result.workflowId}) in cloud.`, "Template applied");
2387
1539
  }
2388
1540
  catch (err) {
2389
1541
  console.error(err instanceof Error ? err.message : String(err));
@@ -2391,21 +1543,10 @@ async function runDefaultOnboardingFlow(cwd) {
2391
1543
  }
2392
1544
  }
2393
1545
  }
2394
- else if (mode === "cloud" && cloudCurrentWorkflowId == null) {
2395
- cloudCurrentWorkflowId =
2396
- index?.cloud_current_workflow_id ?? cloudWorkflowList[0]?.id ?? null;
2397
- }
2398
- if (mode === "local") {
2399
- const workflowIdToOpen = localCurrentWorkflowId ??
2400
- (await readWorkflowIndexOptional(cwd).then((idx) => idx?.current_workflow_id ?? null));
2401
- await launchLocalStudio(cwd, undefined, workflowIdToOpen);
2402
- }
2403
- else {
2404
- const appUrl = getCloudAppUrl();
2405
- const url = buildCloudOnboardingUrl(appUrl, cloudCurrentWorkflowId);
2406
- await openUrl(url);
2407
- printOpenedUrlMessage(url, { workflow: Boolean(cloudCurrentWorkflowId) });
1546
+ else if (cloudCurrentWorkflowId == null) {
1547
+ cloudCurrentWorkflowId = index?.cloud_current_workflow_id ?? cloudWorkflowList[0]?.id ?? null;
2408
1548
  }
1549
+ await runLocalStudioForeground(cwd, { existingHandle: loginServerHandle });
2409
1550
  }
2410
1551
  program.action(async () => {
2411
1552
  const opts = program.opts();
@@ -2415,9 +1556,7 @@ program.action(async () => {
2415
1556
  }
2416
1557
  const cwd = process.cwd();
2417
1558
  if (!process.stdin.isTTY) {
2418
- const appUrl = getCloudAppUrl();
2419
- await openUrl(appUrl);
2420
- printOpenedUrlMessage(appUrl, { workflow: false });
1559
+ await runLocalStudioForeground(cwd);
2421
1560
  return;
2422
1561
  }
2423
1562
  await runDefaultOnboardingFlow(cwd);