clawup 1.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 (190) hide show
  1. package/README.md +245 -0
  2. package/dist/adapters/api-adapter.d.ts +76 -0
  3. package/dist/adapters/api-adapter.js +250 -0
  4. package/dist/adapters/api-adapter.js.map +1 -0
  5. package/dist/adapters/cli-adapter.d.ts +15 -0
  6. package/dist/adapters/cli-adapter.js +208 -0
  7. package/dist/adapters/cli-adapter.js.map +1 -0
  8. package/dist/adapters/index.d.ts +22 -0
  9. package/dist/adapters/index.js +32 -0
  10. package/dist/adapters/index.js.map +1 -0
  11. package/dist/adapters/types.d.ts +135 -0
  12. package/dist/adapters/types.js +14 -0
  13. package/dist/adapters/types.js.map +1 -0
  14. package/dist/bin.d.ts +8 -0
  15. package/dist/bin.js +221 -0
  16. package/dist/bin.js.map +1 -0
  17. package/dist/commands/config.d.ts +21 -0
  18. package/dist/commands/config.js +323 -0
  19. package/dist/commands/config.js.map +1 -0
  20. package/dist/commands/deploy.d.ts +7 -0
  21. package/dist/commands/deploy.js +13 -0
  22. package/dist/commands/deploy.js.map +1 -0
  23. package/dist/commands/destroy.d.ts +7 -0
  24. package/dist/commands/destroy.js +13 -0
  25. package/dist/commands/destroy.js.map +1 -0
  26. package/dist/commands/init.d.ts +13 -0
  27. package/dist/commands/init.js +698 -0
  28. package/dist/commands/init.js.map +1 -0
  29. package/dist/commands/list.d.ts +8 -0
  30. package/dist/commands/list.js +42 -0
  31. package/dist/commands/list.js.map +1 -0
  32. package/dist/commands/push.d.ts +7 -0
  33. package/dist/commands/push.js +19 -0
  34. package/dist/commands/push.js.map +1 -0
  35. package/dist/commands/redeploy.d.ts +7 -0
  36. package/dist/commands/redeploy.js +13 -0
  37. package/dist/commands/redeploy.js.map +1 -0
  38. package/dist/commands/secrets.d.ts +16 -0
  39. package/dist/commands/secrets.js +169 -0
  40. package/dist/commands/secrets.js.map +1 -0
  41. package/dist/commands/ssh.d.ts +9 -0
  42. package/dist/commands/ssh.js +108 -0
  43. package/dist/commands/ssh.js.map +1 -0
  44. package/dist/commands/status.d.ts +7 -0
  45. package/dist/commands/status.js +13 -0
  46. package/dist/commands/status.js.map +1 -0
  47. package/dist/commands/update.d.ts +7 -0
  48. package/dist/commands/update.js +126 -0
  49. package/dist/commands/update.js.map +1 -0
  50. package/dist/commands/validate.d.ts +7 -0
  51. package/dist/commands/validate.js +13 -0
  52. package/dist/commands/validate.js.map +1 -0
  53. package/dist/commands/webhooks.d.ts +7 -0
  54. package/dist/commands/webhooks.js +13 -0
  55. package/dist/commands/webhooks.js.map +1 -0
  56. package/dist/lib/__tests__/identity.test.d.ts +1 -0
  57. package/dist/lib/__tests__/identity.test.js +186 -0
  58. package/dist/lib/__tests__/identity.test.js.map +1 -0
  59. package/dist/lib/__tests__/validate-agent.test.d.ts +1 -0
  60. package/dist/lib/__tests__/validate-agent.test.js +38 -0
  61. package/dist/lib/__tests__/validate-agent.test.js.map +1 -0
  62. package/dist/lib/config.d.ts +69 -0
  63. package/dist/lib/config.js +218 -0
  64. package/dist/lib/config.js.map +1 -0
  65. package/dist/lib/constants.d.ts +5 -0
  66. package/dist/lib/constants.js +29 -0
  67. package/dist/lib/constants.js.map +1 -0
  68. package/dist/lib/exec.d.ts +24 -0
  69. package/dist/lib/exec.js +63 -0
  70. package/dist/lib/exec.js.map +1 -0
  71. package/dist/lib/prerequisites.d.ts +8 -0
  72. package/dist/lib/prerequisites.js +146 -0
  73. package/dist/lib/prerequisites.js.map +1 -0
  74. package/dist/lib/process.d.ts +18 -0
  75. package/dist/lib/process.js +37 -0
  76. package/dist/lib/process.js.map +1 -0
  77. package/dist/lib/pulumi.d.ts +37 -0
  78. package/dist/lib/pulumi.js +87 -0
  79. package/dist/lib/pulumi.js.map +1 -0
  80. package/dist/lib/tailscale.d.ts +75 -0
  81. package/dist/lib/tailscale.js +251 -0
  82. package/dist/lib/tailscale.js.map +1 -0
  83. package/dist/lib/tool-helpers.d.ts +15 -0
  84. package/dist/lib/tool-helpers.js +35 -0
  85. package/dist/lib/tool-helpers.js.map +1 -0
  86. package/dist/lib/ui.d.ts +26 -0
  87. package/dist/lib/ui.js +86 -0
  88. package/dist/lib/ui.js.map +1 -0
  89. package/dist/lib/update-check.d.ts +8 -0
  90. package/dist/lib/update-check.js +151 -0
  91. package/dist/lib/update-check.js.map +1 -0
  92. package/dist/lib/vendor.d.ts +34 -0
  93. package/dist/lib/vendor.js +128 -0
  94. package/dist/lib/vendor.js.map +1 -0
  95. package/dist/lib/workspace.d.ts +21 -0
  96. package/dist/lib/workspace.js +170 -0
  97. package/dist/lib/workspace.js.map +1 -0
  98. package/dist/tools/deploy.d.ts +16 -0
  99. package/dist/tools/deploy.js +181 -0
  100. package/dist/tools/deploy.js.map +1 -0
  101. package/dist/tools/destroy.d.ts +16 -0
  102. package/dist/tools/destroy.js +119 -0
  103. package/dist/tools/destroy.js.map +1 -0
  104. package/dist/tools/index.d.ts +20 -0
  105. package/dist/tools/index.js +34 -0
  106. package/dist/tools/index.js.map +1 -0
  107. package/dist/tools/push.d.ts +29 -0
  108. package/dist/tools/push.js +341 -0
  109. package/dist/tools/push.js.map +1 -0
  110. package/dist/tools/redeploy.d.ts +17 -0
  111. package/dist/tools/redeploy.js +181 -0
  112. package/dist/tools/redeploy.js.map +1 -0
  113. package/dist/tools/status.d.ts +16 -0
  114. package/dist/tools/status.js +205 -0
  115. package/dist/tools/status.js.map +1 -0
  116. package/dist/tools/validate.d.ts +16 -0
  117. package/dist/tools/validate.js +219 -0
  118. package/dist/tools/validate.js.map +1 -0
  119. package/dist/tools/webhooks.d.ts +17 -0
  120. package/dist/tools/webhooks.js +181 -0
  121. package/dist/tools/webhooks.js.map +1 -0
  122. package/dist/types.d.ts +6 -0
  123. package/dist/types.js +10 -0
  124. package/dist/types.js.map +1 -0
  125. package/infra/Pulumi.yaml +6 -0
  126. package/infra/dist/components/cloud-init.js +412 -0
  127. package/infra/dist/components/config-generator.js +254 -0
  128. package/infra/dist/components/hetzner-agent.js +162 -0
  129. package/infra/dist/components/index.js +18 -0
  130. package/infra/dist/components/openclaw-agent.js +287 -0
  131. package/infra/dist/components/shared.js +132 -0
  132. package/infra/dist/components/types.js +6 -0
  133. package/infra/dist/index.js +387 -0
  134. package/infra/dist/shared-vpc.js +167 -0
  135. package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.d.ts +2 -0
  136. package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.d.ts.map +1 -0
  137. package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.js +124 -0
  138. package/infra/node_modules/@clawup/core/dist/__tests__/schemas.test.js.map +1 -0
  139. package/infra/node_modules/@clawup/core/dist/coding-agent-registry.d.ts +32 -0
  140. package/infra/node_modules/@clawup/core/dist/coding-agent-registry.d.ts.map +1 -0
  141. package/infra/node_modules/@clawup/core/dist/coding-agent-registry.js +56 -0
  142. package/infra/node_modules/@clawup/core/dist/coding-agent-registry.js.map +1 -0
  143. package/infra/node_modules/@clawup/core/dist/constants.d.ts +137 -0
  144. package/infra/node_modules/@clawup/core/dist/constants.d.ts.map +1 -0
  145. package/infra/node_modules/@clawup/core/dist/constants.js +314 -0
  146. package/infra/node_modules/@clawup/core/dist/constants.js.map +1 -0
  147. package/infra/node_modules/@clawup/core/dist/dep-registry.d.ts +25 -0
  148. package/infra/node_modules/@clawup/core/dist/dep-registry.d.ts.map +1 -0
  149. package/infra/node_modules/@clawup/core/dist/dep-registry.js +46 -0
  150. package/infra/node_modules/@clawup/core/dist/dep-registry.js.map +1 -0
  151. package/infra/node_modules/@clawup/core/dist/deps.d.ts +18 -0
  152. package/infra/node_modules/@clawup/core/dist/deps.d.ts.map +1 -0
  153. package/infra/node_modules/@clawup/core/dist/deps.js +39 -0
  154. package/infra/node_modules/@clawup/core/dist/deps.js.map +1 -0
  155. package/infra/node_modules/@clawup/core/dist/identity.d.ts +20 -0
  156. package/infra/node_modules/@clawup/core/dist/identity.d.ts.map +1 -0
  157. package/infra/node_modules/@clawup/core/dist/identity.js +217 -0
  158. package/infra/node_modules/@clawup/core/dist/identity.js.map +1 -0
  159. package/infra/node_modules/@clawup/core/dist/index.d.ts +18 -0
  160. package/infra/node_modules/@clawup/core/dist/index.d.ts.map +1 -0
  161. package/infra/node_modules/@clawup/core/dist/index.js +52 -0
  162. package/infra/node_modules/@clawup/core/dist/index.js.map +1 -0
  163. package/infra/node_modules/@clawup/core/dist/plugin-registry.d.ts +13 -0
  164. package/infra/node_modules/@clawup/core/dist/plugin-registry.d.ts.map +1 -0
  165. package/infra/node_modules/@clawup/core/dist/plugin-registry.js +24 -0
  166. package/infra/node_modules/@clawup/core/dist/plugin-registry.js.map +1 -0
  167. package/infra/node_modules/@clawup/core/dist/schemas/identity.d.ts +74 -0
  168. package/infra/node_modules/@clawup/core/dist/schemas/identity.d.ts.map +1 -0
  169. package/infra/node_modules/@clawup/core/dist/schemas/identity.js +45 -0
  170. package/infra/node_modules/@clawup/core/dist/schemas/identity.js.map +1 -0
  171. package/infra/node_modules/@clawup/core/dist/schemas/index.d.ts +6 -0
  172. package/infra/node_modules/@clawup/core/dist/schemas/index.d.ts.map +1 -0
  173. package/infra/node_modules/@clawup/core/dist/schemas/index.js +13 -0
  174. package/infra/node_modules/@clawup/core/dist/schemas/index.js.map +1 -0
  175. package/infra/node_modules/@clawup/core/dist/schemas/manifest.d.ts +159 -0
  176. package/infra/node_modules/@clawup/core/dist/schemas/manifest.d.ts.map +1 -0
  177. package/infra/node_modules/@clawup/core/dist/schemas/manifest.js +54 -0
  178. package/infra/node_modules/@clawup/core/dist/schemas/manifest.js.map +1 -0
  179. package/infra/node_modules/@clawup/core/dist/skills.d.ts +30 -0
  180. package/infra/node_modules/@clawup/core/dist/skills.d.ts.map +1 -0
  181. package/infra/node_modules/@clawup/core/dist/skills.js +52 -0
  182. package/infra/node_modules/@clawup/core/dist/skills.js.map +1 -0
  183. package/infra/node_modules/@clawup/core/dist/types.d.ts +59 -0
  184. package/infra/node_modules/@clawup/core/dist/types.d.ts.map +1 -0
  185. package/infra/node_modules/@clawup/core/dist/types.js +30 -0
  186. package/infra/node_modules/@clawup/core/dist/types.js.map +1 -0
  187. package/infra/node_modules/@clawup/core/package.json +46 -0
  188. package/infra/package.json +12 -0
  189. package/package.json +43 -0
  190. package/scripts/postinstall.mjs +395 -0
@@ -0,0 +1,698 @@
1
+ "use strict";
2
+ /**
3
+ * clawup init — Interactive setup wizard
4
+ *
5
+ * Identity-driven: every agent must have an identity source.
6
+ * The manifest stores only team composition (which agents to deploy).
7
+ * Plugins, deps, and config come from identities at deploy time.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.initCommand = initCommand;
44
+ const child_process_1 = require("child_process");
45
+ const p = __importStar(require("@clack/prompts"));
46
+ const core_1 = require("@clawup/core");
47
+ const identity_1 = require("@clawup/core/identity");
48
+ const os = __importStar(require("os"));
49
+ const path = __importStar(require("path"));
50
+ const prerequisites_1 = require("../lib/prerequisites");
51
+ const pulumi_1 = require("../lib/pulumi");
52
+ const config_1 = require("../lib/config");
53
+ const workspace_1 = require("../lib/workspace");
54
+ const ui_1 = require("../lib/ui");
55
+ async function initCommand(opts = {}) {
56
+ (0, ui_1.showBanner)();
57
+ // -------------------------------------------------------------------------
58
+ // Step 1: Check prerequisites
59
+ // -------------------------------------------------------------------------
60
+ p.log.step("Checking prerequisites...");
61
+ const prereqsOk = await (0, prerequisites_1.checkPrerequisites)();
62
+ if (!prereqsOk) {
63
+ (0, ui_1.exitWithError)("Prerequisites not met. Please install the missing tools and try again.");
64
+ }
65
+ p.log.success("All prerequisites satisfied!");
66
+ // -------------------------------------------------------------------------
67
+ // Step 2: Infrastructure config
68
+ // -------------------------------------------------------------------------
69
+ const stackName = await p.text({
70
+ message: "Pulumi stack name",
71
+ placeholder: "dev",
72
+ defaultValue: "dev",
73
+ });
74
+ (0, ui_1.handleCancel)(stackName);
75
+ const provider = await p.select({
76
+ message: "Cloud provider",
77
+ options: core_1.PROVIDERS.map((prov) => ({ value: prov.value, label: prov.label, hint: prov.hint })),
78
+ initialValue: "aws",
79
+ });
80
+ (0, ui_1.handleCancel)(provider);
81
+ let region;
82
+ let instanceType;
83
+ if (provider === "aws") {
84
+ const awsRegion = await p.select({
85
+ message: "AWS region",
86
+ options: core_1.AWS_REGIONS,
87
+ initialValue: "us-east-1",
88
+ });
89
+ (0, ui_1.handleCancel)(awsRegion);
90
+ region = awsRegion;
91
+ const awsInstanceType = await p.select({
92
+ message: "Default instance type",
93
+ options: core_1.INSTANCE_TYPES,
94
+ initialValue: "t3.medium",
95
+ });
96
+ (0, ui_1.handleCancel)(awsInstanceType);
97
+ instanceType = awsInstanceType;
98
+ }
99
+ else {
100
+ const hetznerLocation = await p.select({
101
+ message: "Hetzner location",
102
+ options: core_1.HETZNER_LOCATIONS,
103
+ initialValue: "fsn1",
104
+ });
105
+ (0, ui_1.handleCancel)(hetznerLocation);
106
+ region = hetznerLocation;
107
+ const serverTypeOptions = (0, core_1.hetznerServerTypes)(region);
108
+ const hetznerServerType = await p.select({
109
+ message: "Default server type",
110
+ options: serverTypeOptions,
111
+ initialValue: serverTypeOptions[0].value,
112
+ });
113
+ (0, ui_1.handleCancel)(hetznerServerType);
114
+ instanceType = hetznerServerType;
115
+ }
116
+ // -------------------------------------------------------------------------
117
+ // Step 3: Owner info
118
+ // -------------------------------------------------------------------------
119
+ const ownerName = await p.text({
120
+ message: "Owner name (for workspace templates)",
121
+ placeholder: "Boss",
122
+ defaultValue: "Boss",
123
+ });
124
+ (0, ui_1.handleCancel)(ownerName);
125
+ const timezone = await p.text({
126
+ message: "Your timezone",
127
+ placeholder: "PST (America/Los_Angeles)",
128
+ defaultValue: "PST (America/Los_Angeles)",
129
+ });
130
+ (0, ui_1.handleCancel)(timezone);
131
+ const workingHours = await p.text({
132
+ message: "Your working hours",
133
+ placeholder: "9am-6pm",
134
+ defaultValue: "9am-6pm",
135
+ });
136
+ (0, ui_1.handleCancel)(workingHours);
137
+ const userNotes = await p.text({
138
+ message: "Any notes for your agents about you? (optional)",
139
+ placeholder: "e.g., Prefers concise updates, hates unnecessary meetings",
140
+ defaultValue: "",
141
+ });
142
+ (0, ui_1.handleCancel)(userNotes);
143
+ const basicConfig = {
144
+ stackName: stackName,
145
+ provider: provider,
146
+ region,
147
+ instanceType,
148
+ ownerName: ownerName,
149
+ timezone: timezone,
150
+ workingHours: workingHours,
151
+ userNotes: userNotes || "No additional notes provided yet.",
152
+ };
153
+ // -------------------------------------------------------------------------
154
+ // Step 4: Secrets (Anthropic, Tailscale, Hetzner)
155
+ // -------------------------------------------------------------------------
156
+ p.log.step("Configure Anthropic API key");
157
+ p.note(core_1.KEY_INSTRUCTIONS.anthropicApiKey.steps.join("\n"), core_1.KEY_INSTRUCTIONS.anthropicApiKey.title);
158
+ const anthropicApiKey = await p.text({
159
+ message: "Anthropic API key",
160
+ placeholder: `${core_1.MODEL_PROVIDERS.anthropic.keyPrefix}...`,
161
+ validate: (val) => {
162
+ if (!val)
163
+ return "API key is required";
164
+ if (!val.startsWith(core_1.MODEL_PROVIDERS.anthropic.keyPrefix)) {
165
+ return `Must start with ${core_1.MODEL_PROVIDERS.anthropic.keyPrefix}`;
166
+ }
167
+ },
168
+ });
169
+ (0, ui_1.handleCancel)(anthropicApiKey);
170
+ p.log.step("Configure infrastructure secrets");
171
+ p.note(core_1.KEY_INSTRUCTIONS.tailscaleAuthKey.steps.join("\n"), core_1.KEY_INSTRUCTIONS.tailscaleAuthKey.title);
172
+ const tailscaleAuthKey = await p.password({
173
+ message: "Tailscale auth key",
174
+ validate: (val) => {
175
+ if (!val.startsWith("tskey-auth-"))
176
+ return "Must start with tskey-auth-";
177
+ },
178
+ });
179
+ (0, ui_1.handleCancel)(tailscaleAuthKey);
180
+ p.note(core_1.KEY_INSTRUCTIONS.tailnetDnsName.steps.join("\n"), core_1.KEY_INSTRUCTIONS.tailnetDnsName.title);
181
+ const tailnetDnsName = await p.text({
182
+ message: "Tailnet DNS name",
183
+ placeholder: "my-tailnet.ts.net",
184
+ validate: (val) => {
185
+ if (!val.endsWith(".ts.net"))
186
+ return "Must end with .ts.net";
187
+ },
188
+ });
189
+ (0, ui_1.handleCancel)(tailnetDnsName);
190
+ p.note(core_1.KEY_INSTRUCTIONS.tailscaleApiKey.steps.join("\n"), core_1.KEY_INSTRUCTIONS.tailscaleApiKey.title);
191
+ const tailscaleApiKey = await p.text({
192
+ message: "Tailscale API key (press Enter to skip)",
193
+ placeholder: "tskey-api-... (optional)",
194
+ defaultValue: "",
195
+ });
196
+ (0, ui_1.handleCancel)(tailscaleApiKey);
197
+ let hcloudToken;
198
+ if (provider === "hetzner") {
199
+ p.note(core_1.KEY_INSTRUCTIONS.hcloudToken.steps.join("\n"), core_1.KEY_INSTRUCTIONS.hcloudToken.title);
200
+ const token = await p.password({
201
+ message: "Hetzner Cloud API token",
202
+ validate: (val) => {
203
+ if (!val)
204
+ return "API token is required for Hetzner deployments";
205
+ },
206
+ });
207
+ (0, ui_1.handleCancel)(token);
208
+ hcloudToken = token;
209
+ }
210
+ // -------------------------------------------------------------------------
211
+ // Step 5: Agent selection (identity-driven, no custom mode)
212
+ // -------------------------------------------------------------------------
213
+ p.log.step("Configure agents");
214
+ const agentMode = await p.select({
215
+ message: "How would you like to configure agents?",
216
+ options: [
217
+ { value: "built-in", label: "Built-in agents", hint: "PM (Juno), Eng (Titus), QA (Scout)" },
218
+ { value: "identity", label: "From identity source", hint: "Load from a Git URL or local path" },
219
+ { value: "mix", label: "Mix of both", hint: "Pick built-in + add from identity source" },
220
+ ],
221
+ });
222
+ (0, ui_1.handleCancel)(agentMode);
223
+ const fetchedIdentities = [];
224
+ const identityCacheDir = path.join(os.homedir(), ".clawup", "identity-cache");
225
+ // Collect built-in agents
226
+ if (agentMode === "built-in" || agentMode === "mix") {
227
+ const selectedBuiltIns = await p.multiselect({
228
+ message: "Select agents",
229
+ options: Object.entries(core_1.BUILT_IN_IDENTITIES).map(([key, entry]) => ({
230
+ value: key,
231
+ label: entry.label,
232
+ hint: entry.hint,
233
+ })),
234
+ required: agentMode === "built-in",
235
+ });
236
+ (0, ui_1.handleCancel)(selectedBuiltIns);
237
+ for (const key of selectedBuiltIns) {
238
+ const entry = core_1.BUILT_IN_IDENTITIES[key];
239
+ const identity = await (0, identity_1.fetchIdentity)(entry.path, identityCacheDir);
240
+ const agent = {
241
+ name: `agent-${identity.manifest.name}`,
242
+ displayName: identity.manifest.displayName,
243
+ role: identity.manifest.role,
244
+ identity: entry.path,
245
+ volumeSize: identity.manifest.volumeSize,
246
+ };
247
+ fetchedIdentities.push({ agent, manifest: identity.manifest });
248
+ }
249
+ }
250
+ // Collect identity-source agents
251
+ if (agentMode === "identity" || agentMode === "mix") {
252
+ let addMore = true;
253
+ while (addMore) {
254
+ const identityUrl = await p.text({
255
+ message: "Identity source (Git URL or local path)",
256
+ placeholder: "https://github.com/org/identities#agent-name",
257
+ validate: (val) => {
258
+ if (!val.trim())
259
+ return "Identity source is required";
260
+ },
261
+ });
262
+ (0, ui_1.handleCancel)(identityUrl);
263
+ const spinner = p.spinner();
264
+ spinner.start("Validating identity...");
265
+ try {
266
+ const identity = await (0, identity_1.fetchIdentity)(identityUrl, identityCacheDir);
267
+ spinner.stop(`Found: ${identity.manifest.displayName} (${identity.manifest.role}) — ${identity.manifest.description}`);
268
+ // Allow volume size override
269
+ const volumeOverride = await p.text({
270
+ message: `Volume size in GB (default: ${identity.manifest.volumeSize})`,
271
+ placeholder: String(identity.manifest.volumeSize),
272
+ defaultValue: String(identity.manifest.volumeSize),
273
+ validate: (val) => {
274
+ const n = parseInt(val, 10);
275
+ if (isNaN(n) || n < 8 || n > 500)
276
+ return "Must be between 8 and 500";
277
+ },
278
+ });
279
+ (0, ui_1.handleCancel)(volumeOverride);
280
+ const agent = {
281
+ name: `agent-${identity.manifest.name}`,
282
+ displayName: identity.manifest.displayName,
283
+ role: identity.manifest.role,
284
+ identity: identityUrl,
285
+ volumeSize: parseInt(volumeOverride, 10),
286
+ };
287
+ fetchedIdentities.push({ agent, manifest: identity.manifest });
288
+ }
289
+ catch (err) {
290
+ spinner.stop(`Failed to validate identity: ${err.message}`);
291
+ p.log.error("Please check the URL and try again.");
292
+ continue;
293
+ }
294
+ const more = await p.confirm({
295
+ message: "Add another identity-based agent?",
296
+ initialValue: false,
297
+ });
298
+ (0, ui_1.handleCancel)(more);
299
+ addMore = more;
300
+ }
301
+ }
302
+ if (fetchedIdentities.length === 0) {
303
+ (0, ui_1.exitWithError)("No agents configured. At least one agent is required.");
304
+ }
305
+ const agents = fetchedIdentities.map((fi) => fi.agent);
306
+ // -------------------------------------------------------------------------
307
+ // Step 6: Collect template variable values
308
+ // -------------------------------------------------------------------------
309
+ // Auto-fillable vars from owner info
310
+ const autoVars = {
311
+ OWNER_NAME: basicConfig.ownerName,
312
+ TIMEZONE: basicConfig.timezone,
313
+ WORKING_HOURS: basicConfig.workingHours,
314
+ USER_NOTES: basicConfig.userNotes,
315
+ };
316
+ // Scan all identities for template vars and deduplicate
317
+ const allTemplateVarNames = new Set();
318
+ for (const fi of fetchedIdentities) {
319
+ for (const v of fi.manifest.templateVars ?? []) {
320
+ allTemplateVarNames.add(v);
321
+ }
322
+ }
323
+ const templateVars = {};
324
+ // Auto-fill known vars
325
+ for (const varName of allTemplateVarNames) {
326
+ if (autoVars[varName]) {
327
+ templateVars[varName] = autoVars[varName];
328
+ }
329
+ }
330
+ // Prompt for remaining vars
331
+ const remainingVars = [...allTemplateVarNames].filter((v) => !templateVars[v]);
332
+ if (remainingVars.length > 0) {
333
+ p.log.step("Configure template variables");
334
+ p.log.info(`Your agents use the following template variables: ${remainingVars.join(", ")}`);
335
+ for (const varName of remainingVars) {
336
+ const value = await p.text({
337
+ message: `Value for ${varName}`,
338
+ placeholder: varName === "LINEAR_TEAM" ? "e.g., ENG" : varName === "GITHUB_REPO" ? "https://github.com/org/repo" : "",
339
+ validate: (val) => {
340
+ if (!val.trim())
341
+ return `${varName} is required`;
342
+ },
343
+ });
344
+ (0, ui_1.handleCancel)(value);
345
+ templateVars[varName] = value;
346
+ }
347
+ }
348
+ // -------------------------------------------------------------------------
349
+ // Step 7: Collect integration credentials (driven by identity plugins/deps)
350
+ // -------------------------------------------------------------------------
351
+ p.log.step("Configure integrations");
352
+ // Determine which plugins and deps are needed across all identities
353
+ const agentPlugins = new Map(); // agent name → plugin names
354
+ const agentDeps = new Map(); // agent name → dep names
355
+ const allPluginNames = new Set();
356
+ const allDepNames = new Set();
357
+ for (const fi of fetchedIdentities) {
358
+ const plugins = new Set(fi.manifest.plugins ?? []);
359
+ const deps = new Set(fi.manifest.deps ?? []);
360
+ agentPlugins.set(fi.agent.name, plugins);
361
+ agentDeps.set(fi.agent.name, deps);
362
+ for (const pl of plugins)
363
+ allPluginNames.add(pl);
364
+ for (const d of deps)
365
+ allDepNames.add(d);
366
+ }
367
+ // Track identity pluginDefaults per agent name for seeding plugin config files
368
+ const identityPluginDefaults = {};
369
+ for (const fi of fetchedIdentities) {
370
+ if (fi.manifest.pluginDefaults) {
371
+ identityPluginDefaults[fi.agent.name] = fi.manifest.pluginDefaults;
372
+ }
373
+ }
374
+ // Per-agent integration credentials
375
+ const integrationCredentials = {};
376
+ for (const agent of agents) {
377
+ integrationCredentials[agent.role] = {};
378
+ }
379
+ // Slack credentials — only if any identity has the slack plugin
380
+ const slackCredentials = {};
381
+ if (allPluginNames.has("slack")) {
382
+ p.note(core_1.KEY_INSTRUCTIONS.slackCredentials.steps.join("\n"), core_1.KEY_INSTRUCTIONS.slackCredentials.title);
383
+ for (const fi of fetchedIdentities) {
384
+ if (!agentPlugins.get(fi.agent.name)?.has("slack"))
385
+ continue;
386
+ const slackManifest = (0, core_1.slackAppManifest)(fi.agent.displayName);
387
+ try {
388
+ (0, child_process_1.execSync)(process.platform === "darwin" ? "pbcopy" : "xclip -selection clipboard", { input: slackManifest });
389
+ p.log.success(`Slack manifest for ${fi.agent.displayName} copied to clipboard — paste it into Slack`);
390
+ }
391
+ catch {
392
+ p.log.warn(`Could not copy to clipboard. Manifest for ${fi.agent.displayName}:`);
393
+ console.log(slackManifest);
394
+ }
395
+ const botToken = await p.password({
396
+ message: `Slack Bot Token for ${fi.agent.displayName} (${fi.agent.role})`,
397
+ validate: (val) => {
398
+ if (!val.startsWith("xoxb-"))
399
+ return "Must start with xoxb-";
400
+ },
401
+ });
402
+ (0, ui_1.handleCancel)(botToken);
403
+ const appToken = await p.password({
404
+ message: `Slack App Token for ${fi.agent.displayName} (${fi.agent.role})`,
405
+ validate: (val) => {
406
+ if (!val.startsWith("xapp-"))
407
+ return "Must start with xapp-";
408
+ },
409
+ });
410
+ (0, ui_1.handleCancel)(appToken);
411
+ slackCredentials[fi.agent.role] = {
412
+ botToken: botToken,
413
+ appToken: appToken,
414
+ };
415
+ }
416
+ }
417
+ // Linear credentials — only if any identity has the openclaw-linear plugin
418
+ if (allPluginNames.has("openclaw-linear")) {
419
+ p.note(core_1.KEY_INSTRUCTIONS.linearApiKey.steps.join("\n"), core_1.KEY_INSTRUCTIONS.linearApiKey.title);
420
+ const linearAgents = fetchedIdentities.filter((fi) => agentPlugins.get(fi.agent.name)?.has("openclaw-linear"));
421
+ for (const fi of linearAgents) {
422
+ const linearKey = await p.password({
423
+ message: `Linear API key for ${fi.agent.displayName} (${fi.agent.role})`,
424
+ validate: (val) => {
425
+ if (!val.startsWith("lin_api_"))
426
+ return "Must start with lin_api_";
427
+ },
428
+ });
429
+ (0, ui_1.handleCancel)(linearKey);
430
+ integrationCredentials[fi.agent.role].linearApiKey = linearKey;
431
+ // Auto-fetch user UUID from Linear API
432
+ const s = p.spinner();
433
+ s.start(`Fetching Linear user ID for ${fi.agent.displayName}...`);
434
+ try {
435
+ const res = await fetch("https://api.linear.app/graphql", {
436
+ method: "POST",
437
+ headers: {
438
+ "Content-Type": "application/json",
439
+ Authorization: linearKey,
440
+ },
441
+ body: JSON.stringify({ query: "{ viewer { id } }" }),
442
+ });
443
+ const data = (await res.json());
444
+ const uuid = data?.data?.viewer?.id;
445
+ if (!uuid)
446
+ throw new Error("No user ID in response");
447
+ integrationCredentials[fi.agent.role].linearUserUuid = uuid;
448
+ s.stop(`${fi.agent.displayName}: ${uuid}`);
449
+ }
450
+ catch (err) {
451
+ s.stop(`Could not fetch Linear user ID for ${fi.agent.displayName}`);
452
+ p.log.warn(`${err instanceof Error ? err.message : String(err)}`);
453
+ const linearUserUuid = await p.text({
454
+ message: `Enter Linear user UUID manually for ${fi.agent.displayName}`,
455
+ placeholder: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
456
+ validate: (val) => {
457
+ if (!val)
458
+ return "Linear user UUID is required";
459
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(val)) {
460
+ return "Must be a valid UUID format";
461
+ }
462
+ },
463
+ });
464
+ (0, ui_1.handleCancel)(linearUserUuid);
465
+ integrationCredentials[fi.agent.role].linearUserUuid = linearUserUuid;
466
+ }
467
+ }
468
+ // Linear webhook signing secrets
469
+ p.note([
470
+ "Create a webhook in Linear for each agent:",
471
+ "1. Go to Settings → API → Webhooks → \"New webhook\"",
472
+ "2. Paste the webhook URL shown below for each agent",
473
+ "3. Select events to receive (e.g., Issues, Comments)",
474
+ "4. Copy the \"Signing secret\" shown after creating the webhook",
475
+ ].join("\n"), "Linear Webhook Setup");
476
+ for (const fi of linearAgents) {
477
+ const webhookUrl = `https://${(0, core_1.tailscaleHostname)(basicConfig.stackName, fi.agent.name)}.${tailnetDnsName}/hooks/linear`;
478
+ p.log.info(`${fi.agent.displayName} (${fi.agent.role}): ${webhookUrl}`);
479
+ const webhookSecretInput = await p.password({
480
+ message: `Signing secret for ${fi.agent.displayName} (${fi.agent.role})`,
481
+ validate: (val) => {
482
+ if (!val)
483
+ return "Webhook signing secret is required";
484
+ },
485
+ });
486
+ (0, ui_1.handleCancel)(webhookSecretInput);
487
+ integrationCredentials[fi.agent.role].linearWebhookSecret = webhookSecretInput;
488
+ }
489
+ }
490
+ // GitHub token — only if any identity has the gh dep
491
+ if (allDepNames.has("gh")) {
492
+ p.note(core_1.KEY_INSTRUCTIONS.githubToken.steps.join("\n"), core_1.KEY_INSTRUCTIONS.githubToken.title);
493
+ for (const fi of fetchedIdentities) {
494
+ if (!agentDeps.get(fi.agent.name)?.has("gh"))
495
+ continue;
496
+ const githubKey = await p.password({
497
+ message: `GitHub token for ${fi.agent.displayName} (${fi.agent.role})`,
498
+ validate: (val) => {
499
+ if (!val.startsWith("ghp_") && !val.startsWith("github_pat_")) {
500
+ return "Must start with ghp_ or github_pat_";
501
+ }
502
+ },
503
+ });
504
+ (0, ui_1.handleCancel)(githubKey);
505
+ integrationCredentials[fi.agent.role].githubToken = githubKey;
506
+ }
507
+ }
508
+ // Brave Search API key — only if any identity has the brave-search dep (global, once)
509
+ let braveApiKey;
510
+ if (allDepNames.has("brave-search")) {
511
+ p.note(core_1.KEY_INSTRUCTIONS.braveApiKey.steps.join("\n"), core_1.KEY_INSTRUCTIONS.braveApiKey.title);
512
+ const braveKey = await p.password({
513
+ message: "Brave Search API key",
514
+ validate: (val) => {
515
+ if (!val)
516
+ return "API key is required";
517
+ },
518
+ });
519
+ (0, ui_1.handleCancel)(braveKey);
520
+ braveApiKey = braveKey;
521
+ }
522
+ // -------------------------------------------------------------------------
523
+ // Step 8: Summary
524
+ // -------------------------------------------------------------------------
525
+ const costEstimates = basicConfig.provider === "aws" ? core_1.COST_ESTIMATES : core_1.HETZNER_COST_ESTIMATES;
526
+ const costPerAgent = costEstimates[basicConfig.instanceType] ?? 30;
527
+ const totalCost = agents.reduce((sum, a) => {
528
+ const agentCost = costEstimates[a.instanceType ?? basicConfig.instanceType] ?? costPerAgent;
529
+ return sum + agentCost;
530
+ }, 0);
531
+ const integrationNames = [];
532
+ if (allPluginNames.has("openclaw-linear"))
533
+ integrationNames.push("Linear");
534
+ if (allPluginNames.has("slack"))
535
+ integrationNames.push("Slack");
536
+ if (allDepNames.has("gh"))
537
+ integrationNames.push("GitHub CLI");
538
+ if (allDepNames.has("brave-search"))
539
+ integrationNames.push("Brave Search");
540
+ const providerLabel = basicConfig.provider === "aws" ? "AWS" : "Hetzner";
541
+ const regionLabel = basicConfig.provider === "aws" ? "Region" : "Location";
542
+ // Build template vars display (excluding auto-filled owner vars)
543
+ const customVarEntries = Object.entries(templateVars).filter(([k]) => !autoVars[k]);
544
+ const summaryLines = [
545
+ `Stack: ${basicConfig.stackName}`,
546
+ `Provider: ${providerLabel}`,
547
+ `${regionLabel.padEnd(14, " ")} ${basicConfig.region}`,
548
+ `Instance type: ${basicConfig.instanceType}`,
549
+ `Owner: ${basicConfig.ownerName}`,
550
+ `Timezone: ${basicConfig.timezone}`,
551
+ `Working hours: ${basicConfig.workingHours}`,
552
+ ];
553
+ if (customVarEntries.length > 0) {
554
+ for (const [k, v] of customVarEntries) {
555
+ summaryLines.push(`${k.padEnd(14, " ")} ${v}`);
556
+ }
557
+ }
558
+ if (integrationNames.length > 0) {
559
+ summaryLines.push(`Integrations: ${integrationNames.join(", ")}`);
560
+ }
561
+ summaryLines.push(``, `Agents (${agents.length}):`, (0, ui_1.formatAgentList)(agents), ``, `Estimated cost: ${(0, ui_1.formatCost)(totalCost)}`);
562
+ p.note(summaryLines.join("\n"), "Deployment Summary");
563
+ // -------------------------------------------------------------------------
564
+ // Step 9: Confirm
565
+ // -------------------------------------------------------------------------
566
+ const confirmed = await p.confirm({
567
+ message: "Proceed with setup?",
568
+ });
569
+ (0, ui_1.handleCancel)(confirmed);
570
+ if (!confirmed) {
571
+ p.cancel("Setup cancelled.");
572
+ process.exit(0);
573
+ }
574
+ // -------------------------------------------------------------------------
575
+ // Step 10: Execute setup
576
+ // -------------------------------------------------------------------------
577
+ const s = p.spinner();
578
+ // Set up workspace
579
+ s.start("Setting up workspace...");
580
+ const wsResult = (0, workspace_1.ensureWorkspace)();
581
+ if (!wsResult.ok) {
582
+ s.stop("Failed to set up workspace");
583
+ (0, ui_1.exitWithError)(wsResult.error ?? "Failed to set up workspace.");
584
+ }
585
+ s.stop("Workspace ready");
586
+ const cwd = (0, workspace_1.getWorkspaceDir)();
587
+ // Select/create stack
588
+ s.start("Selecting Pulumi stack...");
589
+ const stackResult = (0, pulumi_1.selectOrCreateStack)(basicConfig.stackName, cwd);
590
+ if (!stackResult.ok) {
591
+ s.stop("Failed to select/create stack");
592
+ if (stackResult.error)
593
+ p.log.error(stackResult.error);
594
+ (0, ui_1.exitWithError)(`Could not select or create Pulumi stack "${basicConfig.stackName}".`);
595
+ }
596
+ s.stop("Pulumi stack ready");
597
+ // Set Pulumi config
598
+ s.start("Setting Pulumi configuration...");
599
+ (0, pulumi_1.setConfig)("provider", basicConfig.provider, false, cwd);
600
+ if (basicConfig.provider === "aws") {
601
+ (0, pulumi_1.setConfig)("aws:region", basicConfig.region, false, cwd);
602
+ }
603
+ else {
604
+ (0, pulumi_1.setConfig)("hetzner:location", basicConfig.region, false, cwd);
605
+ if (hcloudToken) {
606
+ (0, pulumi_1.setConfig)("hcloud:token", hcloudToken, true, cwd);
607
+ }
608
+ }
609
+ (0, pulumi_1.setConfig)("anthropicApiKey", anthropicApiKey, true, cwd);
610
+ (0, pulumi_1.setConfig)("tailscaleAuthKey", tailscaleAuthKey, true, cwd);
611
+ (0, pulumi_1.setConfig)("tailnetDnsName", tailnetDnsName, false, cwd);
612
+ if (tailscaleApiKey) {
613
+ (0, pulumi_1.setConfig)("tailscaleApiKey", tailscaleApiKey, true, cwd);
614
+ }
615
+ (0, pulumi_1.setConfig)("instanceType", basicConfig.instanceType, false, cwd);
616
+ (0, pulumi_1.setConfig)("ownerName", basicConfig.ownerName, false, cwd);
617
+ (0, pulumi_1.setConfig)("timezone", basicConfig.timezone, false, cwd);
618
+ (0, pulumi_1.setConfig)("workingHours", basicConfig.workingHours, false, cwd);
619
+ (0, pulumi_1.setConfig)("userNotes", basicConfig.userNotes, false, cwd);
620
+ // Set per-agent integration credentials
621
+ for (const [role, creds] of Object.entries(integrationCredentials)) {
622
+ if (creds.linearApiKey)
623
+ (0, pulumi_1.setConfig)(`${role}LinearApiKey`, creds.linearApiKey, true, cwd);
624
+ if (creds.linearWebhookSecret)
625
+ (0, pulumi_1.setConfig)(`${role}LinearWebhookSecret`, creds.linearWebhookSecret, true, cwd);
626
+ if (creds.linearUserUuid)
627
+ (0, pulumi_1.setConfig)(`${role}LinearUserUuid`, creds.linearUserUuid, false, cwd);
628
+ if (creds.githubToken)
629
+ (0, pulumi_1.setConfig)(`${role}GithubToken`, creds.githubToken, true, cwd);
630
+ }
631
+ // Set per-agent Slack credentials
632
+ for (const [role, creds] of Object.entries(slackCredentials)) {
633
+ (0, pulumi_1.setConfig)(`${role}SlackBotToken`, creds.botToken, true, cwd);
634
+ (0, pulumi_1.setConfig)(`${role}SlackAppToken`, creds.appToken, true, cwd);
635
+ }
636
+ if (braveApiKey)
637
+ (0, pulumi_1.setConfig)("braveApiKey", braveApiKey, true, cwd);
638
+ s.stop("Configuration saved");
639
+ // Write manifest
640
+ const configName = basicConfig.stackName;
641
+ s.start(`Writing config to ~/.clawup/configs/${configName}.yaml...`);
642
+ // Only include non-auto template vars in manifest (owner vars are derived at deploy time)
643
+ const manifestTemplateVars = {};
644
+ for (const [k, v] of Object.entries(templateVars)) {
645
+ if (!autoVars[k]) {
646
+ manifestTemplateVars[k] = v;
647
+ }
648
+ }
649
+ const manifest = {
650
+ stackName: configName,
651
+ provider: basicConfig.provider,
652
+ region: basicConfig.region,
653
+ instanceType: basicConfig.instanceType,
654
+ ownerName: basicConfig.ownerName,
655
+ timezone: basicConfig.timezone,
656
+ workingHours: basicConfig.workingHours,
657
+ userNotes: basicConfig.userNotes,
658
+ templateVars: Object.keys(manifestTemplateVars).length > 0 ? manifestTemplateVars : undefined,
659
+ agents,
660
+ };
661
+ // Inline plugin config into each agent definition
662
+ for (const fi of fetchedIdentities) {
663
+ const rolePlugins = agentPlugins.get(fi.agent.name);
664
+ if (!rolePlugins || rolePlugins.size === 0)
665
+ continue;
666
+ const inlinePlugins = {};
667
+ const defaults = identityPluginDefaults[fi.agent.name] ?? {};
668
+ for (const pluginName of rolePlugins) {
669
+ const pluginDefaults = defaults[pluginName] ?? {};
670
+ const agentConfig = {
671
+ ...pluginDefaults,
672
+ agentId: fi.agent.name,
673
+ };
674
+ // Layer on user-provided config
675
+ if (pluginName === "openclaw-linear") {
676
+ const creds = integrationCredentials[fi.agent.role];
677
+ if (creds?.linearUserUuid) {
678
+ agentConfig.linearUserUuid = creds.linearUserUuid;
679
+ }
680
+ }
681
+ inlinePlugins[pluginName] = agentConfig;
682
+ }
683
+ if (Object.keys(inlinePlugins).length > 0) {
684
+ fi.agent.plugins = inlinePlugins;
685
+ }
686
+ }
687
+ (0, config_1.saveManifest)(configName, manifest);
688
+ s.stop("Config saved");
689
+ if (opts.deploy) {
690
+ p.log.success("Config saved! Starting deployment...\n");
691
+ const { deployCommand } = await Promise.resolve().then(() => __importStar(require("./deploy.js")));
692
+ await deployCommand({ config: configName, yes: opts.yes });
693
+ }
694
+ else {
695
+ p.outro("Setup complete! Run `clawup deploy` to deploy your agents.");
696
+ }
697
+ }
698
+ //# sourceMappingURL=init.js.map