arkaos 4.34.0 → 4.36.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 (70) hide show
  1. package/THE-ARKAOS-GUIDE.md +1 -1
  2. package/VERSION +1 -1
  3. package/bin/arka-menubar.py +460 -0
  4. package/config/install-profiles.json +75 -0
  5. package/core/runtime/opencode.py +252 -0
  6. package/core/runtime/registry.py +6 -1
  7. package/harness/codex/AGENTS.md +1 -1
  8. package/harness/copilot/copilot-instructions.md +1 -1
  9. package/harness/cursor/rules/arkaos.mdc +2 -2
  10. package/harness/gemini/GEMINI.md +1 -1
  11. package/harness/opencode/AGENTS.md +1 -1
  12. package/harness/opencode/agents/arka-architect-gabriel.md +12 -0
  13. package/harness/opencode/agents/arka-brand-director-valentina.md +12 -0
  14. package/harness/opencode/agents/arka-cfo-helena.md +12 -0
  15. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +12 -0
  16. package/harness/opencode/agents/arka-community-strategist-beatriz.md +12 -0
  17. package/harness/opencode/agents/arka-content-strategist-rafael.md +12 -0
  18. package/harness/opencode/agents/arka-conversion-strategist-ines.md +12 -0
  19. package/harness/opencode/agents/arka-coo-sofia.md +12 -0
  20. package/harness/opencode/agents/arka-copy-director-eduardo.md +12 -0
  21. package/harness/opencode/agents/arka-cqo-marta.md +12 -0
  22. package/harness/opencode/agents/arka-cto-marco.md +12 -0
  23. package/harness/opencode/agents/arka-design-ops-lead-iris.md +12 -0
  24. package/harness/opencode/agents/arka-ecom-director-ricardo.md +12 -0
  25. package/harness/opencode/agents/arka-knowledge-director-clara.md +12 -0
  26. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +12 -0
  27. package/harness/opencode/agents/arka-marketing-director-luna.md +12 -0
  28. package/harness/opencode/agents/arka-ops-lead-daniel.md +12 -0
  29. package/harness/opencode/agents/arka-pm-director-carolina.md +12 -0
  30. package/harness/opencode/agents/arka-revops-lead-vicente.md +12 -0
  31. package/harness/opencode/agents/arka-saas-strategist-tiago.md +12 -0
  32. package/harness/opencode/agents/arka-sales-director-miguel.md +12 -0
  33. package/harness/opencode/agents/arka-strategy-director-tomas.md +12 -0
  34. package/harness/opencode/agents/arka-tech-director-francisca.md +12 -0
  35. package/harness/opencode/agents/arka-tech-lead-paulo.md +12 -0
  36. package/harness/opencode/agents/arka-video-producer-simao.md +12 -0
  37. package/harness/opencode/commands/arka-brand.md +9 -0
  38. package/harness/opencode/commands/arka-community.md +9 -0
  39. package/harness/opencode/commands/arka-content.md +9 -0
  40. package/harness/opencode/commands/arka-dev.md +9 -0
  41. package/harness/opencode/commands/arka-ecom.md +9 -0
  42. package/harness/opencode/commands/arka-fin.md +9 -0
  43. package/harness/opencode/commands/arka-kb.md +9 -0
  44. package/harness/opencode/commands/arka-landing.md +9 -0
  45. package/harness/opencode/commands/arka-lead.md +9 -0
  46. package/harness/opencode/commands/arka-mkt.md +9 -0
  47. package/harness/opencode/commands/arka-ops.md +9 -0
  48. package/harness/opencode/commands/arka-org.md +9 -0
  49. package/harness/opencode/commands/arka-pm.md +9 -0
  50. package/harness/opencode/commands/arka-saas.md +9 -0
  51. package/harness/opencode/commands/arka-sales.md +9 -0
  52. package/harness/opencode/commands/arka-strat.md +9 -0
  53. package/harness/opencode/opencode.json +16 -0
  54. package/harness/zed/.rules +1 -1
  55. package/installer/adapters/opencode.js +118 -0
  56. package/installer/cli.js +31 -1
  57. package/installer/detect-runtime.js +18 -0
  58. package/installer/doctor.js +202 -2
  59. package/installer/index.js +174 -88
  60. package/installer/menubar.js +262 -0
  61. package/installer/product-stats.js +93 -0
  62. package/installer/profile.js +75 -0
  63. package/installer/prompts.js +303 -41
  64. package/installer/services.js +487 -0
  65. package/installer/ui.js +190 -0
  66. package/installer/update.js +212 -95
  67. package/knowledge/skills-manifest.json +1 -1
  68. package/package.json +6 -1
  69. package/pyproject.toml +1 -1
  70. package/scripts/harness_gen.py +98 -1
@@ -2,12 +2,31 @@
2
2
  * Interactive prompts for ArkaOS installer.
3
3
  * Asks user for directories, language, market, preferences.
4
4
  * Nothing is hardcoded — everything comes from the user.
5
+ *
6
+ * Foundation PR-3: the wizard renders through @clack/prompts when the
7
+ * terminal supports it (see installer/ui.js) and falls back to the
8
+ * original readline flow otherwise. Both paths return the same config
9
+ * shape, now including `installProfile` (persisted by index.js;
10
+ * provisioning of the non-essential tiers is PR-4).
5
11
  */
6
12
 
7
13
  import { createInterface } from "node:readline";
8
14
  import { existsSync, readFileSync } from "node:fs";
9
- import { join } from "node:path";
15
+ import { join, dirname } from "node:path";
10
16
  import { homedir } from "node:os";
17
+ import { fileURLToPath } from "node:url";
18
+ import { getUi } from "./ui.js";
19
+ import {
20
+ DEFAULT_PROFILE,
21
+ INSTALL_PROFILES,
22
+ PROFILE_HINTS,
23
+ normalizeProfileFlag,
24
+ } from "./profile.js";
25
+
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ const VERSION = JSON.parse(
28
+ readFileSync(join(__dirname, "..", "package.json"), "utf-8"),
29
+ ).version;
11
30
 
12
31
  // Readline interface is lazily created so headless upgrade runs can
13
32
  // return without ever opening stdin. Eagerly constructing the interface
@@ -64,6 +83,36 @@ function askChoice(question, options) {
64
83
  // the user where their install lives.
65
84
  const DEFAULT_INSTALL_DIR = join(homedir(), ".arkaos");
66
85
 
86
+ // Shared option lists — one source for both the clack and readline wizards.
87
+ const LANGUAGE_OPTIONS = [
88
+ { label: "English", value: "en" },
89
+ { label: "Português", value: "pt" },
90
+ { label: "Español", value: "es" },
91
+ { label: "Français", value: "fr" },
92
+ { label: "Deutsch", value: "de" },
93
+ { label: "Italiano", value: "it" },
94
+ { label: "中文 (Chinese)", value: "zh" },
95
+ { label: "日本語 (Japanese)", value: "ja" },
96
+ { label: "한국어 (Korean)", value: "ko" },
97
+ { label: "Other", value: "other" },
98
+ ];
99
+
100
+ const ROLE_OPTIONS = [
101
+ { label: "Developer / Engineer", value: "developer" },
102
+ { label: "Founder / CEO", value: "founder" },
103
+ { label: "Marketing / Growth", value: "marketing" },
104
+ { label: "Product Manager", value: "product" },
105
+ { label: "Designer", value: "designer" },
106
+ { label: "Consultant / Agency", value: "consultant" },
107
+ { label: "Other", value: "other" },
108
+ ];
109
+
110
+ const PROFILE_OPTIONS = INSTALL_PROFILES.map((value) => ({
111
+ value,
112
+ label: value,
113
+ hint: PROFILE_HINTS[value],
114
+ }));
115
+
67
116
  // When called on an upgrade and a valid profile.json already exists,
68
117
  // honor the rule documented in .claude/rules/node-installer.md:
69
118
  //
@@ -81,6 +130,10 @@ const DEFAULT_INSTALL_DIR = join(homedir(), ".arkaos");
81
130
  //
82
131
  // - User metadata fields (language, market, role, company,
83
132
  // projectsDir, vaultPath) come from profile.json.
133
+ // - installProfile is carried from profile.json when present and
134
+ // valid; older profiles without the field default to "essential"
135
+ // (Foundation PR-3 — the profile only gates provisioning in PR-4,
136
+ // so defaulting is safe for existing installs).
84
137
  // - installDir is the directory we found the profile in.
85
138
  // - Feature flags (installDashboard, installKnowledge,
86
139
  // installTranscription) default to false on upgrade — an upgrade
@@ -93,7 +146,7 @@ const DEFAULT_INSTALL_DIR = join(homedir(), ".arkaos");
93
146
  // Returns null when the short-circuit conditions are not met, which
94
147
  // falls through to the interactive wizard below (fresh install or
95
148
  // upgrade with a corrupt/missing profile).
96
- function loadExistingProfileConfig(installDir) {
149
+ export function loadExistingProfileConfig(installDir) {
97
150
  const profilePath = join(installDir, "profile.json");
98
151
  if (!existsSync(profilePath)) return null;
99
152
  try {
@@ -110,6 +163,8 @@ function loadExistingProfileConfig(installDir) {
110
163
  projectsDir: profile.projectsDir || join(homedir(), "Projects"),
111
164
  vaultPath: profile.vaultPath || "",
112
165
  installDir,
166
+ installProfile:
167
+ normalizeProfileFlag(profile.installProfile) || DEFAULT_PROFILE,
113
168
  // Upgrades preserve the existing install footprint; they do not
114
169
  // re-opt-in to optional features. A user who wants to flip a
115
170
  // feature flag on upgrade can re-run with the wizard.
@@ -130,7 +185,7 @@ function loadExistingProfileConfig(installDir) {
130
185
  }
131
186
  }
132
187
 
133
- export async function runSetupPrompts(isUpgrade = false) {
188
+ export async function runSetupPrompts(isUpgrade = false, opts = {}) {
134
189
  // Headless upgrade short-circuit. See loadExistingProfileConfig.
135
190
  if (isUpgrade) {
136
191
  const existing = loadExistingProfileConfig(DEFAULT_INSTALL_DIR);
@@ -145,6 +200,7 @@ export async function runSetupPrompts(isUpgrade = false) {
145
200
  Role: ${existing.role}
146
201
  Company: ${existing.company || "(not set)"}
147
202
  Install dir: ${existing.installDir}
203
+ Profile: ${existing.installProfile}
148
204
 
149
205
  Optional features are NOT re-installed on upgrade.
150
206
  Re-run without --force to flip feature flags interactively.
@@ -153,6 +209,200 @@ export async function runSetupPrompts(isUpgrade = false) {
153
209
  }
154
210
  }
155
211
 
212
+ // A valid --profile flag skips the profile question in both wizards.
213
+ const profileFlag = normalizeProfileFlag(opts.profileFlag);
214
+
215
+ const ui = await getUi();
216
+ if (ui.isFancy()) {
217
+ return runClackWizard(ui, profileFlag);
218
+ }
219
+ return runReadlineWizard(profileFlag);
220
+ }
221
+
222
+ // ── Fancy wizard (@clack/prompts) ─────────────────────────────────────────
223
+
224
+ async function runClackWizard(ui, profileFlag) {
225
+ const p = ui.clack;
226
+ const pc = ui.colors;
227
+
228
+ // Ctrl+C anywhere cancels the whole install, mirroring the readline
229
+ // wizard's "Installation cancelled." exit.
230
+ const guard = (value) => {
231
+ if (p.isCancel(value)) {
232
+ p.cancel("Installation cancelled.");
233
+ process.exit(0);
234
+ }
235
+ return value;
236
+ };
237
+
238
+ p.intro(pc.inverse(pc.bold(` ▲ ARKA OS v${VERSION} — Setup `)));
239
+
240
+ const config = {};
241
+
242
+ // ── Identity ──
243
+ p.log.step(pc.bold("Identity"));
244
+ config.language = guard(
245
+ await p.select({
246
+ message: "What is your primary language?",
247
+ options: LANGUAGE_OPTIONS.map(({ label, value }) => ({ value, label })),
248
+ initialValue: "en",
249
+ }),
250
+ );
251
+ if (config.language === "other") {
252
+ config.language =
253
+ String(
254
+ guard(
255
+ await p.text({
256
+ message: "Enter language code",
257
+ placeholder: "e.g., nl, pl, ru",
258
+ }),
259
+ ) || "",
260
+ ).trim() || "en";
261
+ }
262
+ config.market = String(
263
+ guard(
264
+ await p.text({
265
+ message: "What is your primary market/country?",
266
+ placeholder: "e.g., United States, Portugal, Global",
267
+ }),
268
+ ) || "",
269
+ ).trim();
270
+ config.role = guard(
271
+ await p.select({
272
+ message: "What best describes your role?",
273
+ options: ROLE_OPTIONS.map(({ label, value }) => ({ value, label })),
274
+ initialValue: "developer",
275
+ }),
276
+ );
277
+ config.company = String(
278
+ guard(
279
+ await p.text({
280
+ message: "Company or organization name (optional)",
281
+ defaultValue: "",
282
+ }),
283
+ ) || "",
284
+ ).trim();
285
+
286
+ // ── Directories ──
287
+ p.log.step(pc.bold("Directories"));
288
+ config.projectsDir =
289
+ String(
290
+ guard(
291
+ await p.text({
292
+ message: "Where are your projects?",
293
+ initialValue: join(homedir(), "Projects"),
294
+ }),
295
+ ) || "",
296
+ ).trim() || join(homedir(), "Projects");
297
+ config.vaultPath = String(
298
+ guard(
299
+ await p.text({
300
+ message: "Where is your Obsidian vault? (leave empty if none)",
301
+ defaultValue: "",
302
+ }),
303
+ ) || "",
304
+ ).trim();
305
+ if (config.vaultPath && !existsSync(config.vaultPath)) {
306
+ const create = guard(
307
+ await p.confirm({
308
+ message: `Directory not found: ${config.vaultPath} — create it?`,
309
+ initialValue: false,
310
+ }),
311
+ );
312
+ if (!create) config.vaultPath = "";
313
+ }
314
+ config.installDir =
315
+ String(
316
+ guard(
317
+ await p.text({
318
+ message: "ArkaOS data directory",
319
+ initialValue: DEFAULT_INSTALL_DIR,
320
+ }),
321
+ ) || "",
322
+ ).trim() || DEFAULT_INSTALL_DIR;
323
+
324
+ // ── Installation profile ──
325
+ p.log.step(pc.bold("Installation profile"));
326
+ if (profileFlag) {
327
+ config.installProfile = profileFlag;
328
+ p.log.info(`Profile: ${profileFlag} (from --profile)`);
329
+ } else {
330
+ config.installProfile = guard(
331
+ await p.select({
332
+ message: "Which installation profile?",
333
+ options: PROFILE_OPTIONS,
334
+ initialValue: DEFAULT_PROFILE,
335
+ }),
336
+ );
337
+ }
338
+
339
+ // ── Optional features ──
340
+ p.log.step(pc.bold("Optional features"));
341
+ config.installDashboard = guard(
342
+ await p.confirm({
343
+ message: "Install monitoring dashboard?",
344
+ initialValue: true,
345
+ }),
346
+ );
347
+ config.installKnowledge = guard(
348
+ await p.confirm({
349
+ message: "Install knowledge base (vector DB)?",
350
+ initialValue: true,
351
+ }),
352
+ );
353
+ config.installTranscription = guard(
354
+ await p.confirm({
355
+ message: "Install audio transcription (Whisper)?",
356
+ initialValue: false,
357
+ }),
358
+ );
359
+
360
+ // ── API keys (optional) ──
361
+ p.log.step(pc.bold("API keys (optional, can be configured later)"));
362
+ config.openaiKey = String(
363
+ guard(
364
+ await p.password({
365
+ message: "OpenAI API key (Whisper, embeddings — Enter to skip)",
366
+ mask: "•",
367
+ }),
368
+ ) || "",
369
+ ).trim();
370
+ config.googleKey = String(
371
+ guard(
372
+ await p.password({
373
+ message: "Google API key (Gemini, Nano Banana — Enter to skip)",
374
+ mask: "•",
375
+ }),
376
+ ) || "",
377
+ ).trim();
378
+ config.falKey = String(
379
+ guard(
380
+ await p.password({
381
+ message: "fal.ai API key (image/video generation — Enter to skip)",
382
+ mask: "•",
383
+ }),
384
+ ) || "",
385
+ ).trim();
386
+
387
+ // ── Summary + confirm ──
388
+ p.note(formatSummary(config), "Configuration Summary");
389
+ const confirmed = guard(
390
+ await p.confirm({
391
+ message: "Proceed with this configuration?",
392
+ initialValue: true,
393
+ }),
394
+ );
395
+ if (!confirmed) {
396
+ p.cancel("Installation cancelled.");
397
+ process.exit(0);
398
+ }
399
+ p.outro("Configuration complete — installing.");
400
+ return config;
401
+ }
402
+
403
+ // ── Plain wizard (readline fallback) ──────────────────────────────────────
404
+
405
+ async function runReadlineWizard(profileFlag) {
156
406
  console.log(`
157
407
  ╔══════════════════════════════════════════════════════╗
158
408
  ║ ArkaOS Setup — Let's configure your environment ║
@@ -162,18 +412,10 @@ export async function runSetupPrompts(isUpgrade = false) {
162
412
  const config = {};
163
413
 
164
414
  // ── Language ──
165
- config.language = await askChoice("What is your primary language?", [
166
- { label: "English", value: "en" },
167
- { label: "Português", value: "pt" },
168
- { label: "Español", value: "es" },
169
- { label: "Français", value: "fr" },
170
- { label: "Deutsch", value: "de" },
171
- { label: "Italiano", value: "it" },
172
- { label: "中文 (Chinese)", value: "zh" },
173
- { label: "日本語 (Japanese)", value: "ja" },
174
- { label: "한국어 (Korean)", value: "ko" },
175
- { label: "Other", value: "other" },
176
- ]);
415
+ config.language = await askChoice(
416
+ "What is your primary language?",
417
+ LANGUAGE_OPTIONS,
418
+ );
177
419
 
178
420
  if (config.language === "other") {
179
421
  config.language = await ask("Enter language code (e.g., nl, pl, ru)");
@@ -187,15 +429,7 @@ export async function runSetupPrompts(isUpgrade = false) {
187
429
  }
188
430
 
189
431
  // ── Role ──
190
- config.role = await askChoice("What best describes your role?", [
191
- { label: "Developer / Engineer", value: "developer" },
192
- { label: "Founder / CEO", value: "founder" },
193
- { label: "Marketing / Growth", value: "marketing" },
194
- { label: "Product Manager", value: "product" },
195
- { label: "Designer", value: "designer" },
196
- { label: "Consultant / Agency", value: "consultant" },
197
- { label: "Other", value: "other" },
198
- ]);
432
+ config.role = await askChoice("What best describes your role?", ROLE_OPTIONS);
199
433
 
200
434
  // ── Company ──
201
435
  config.company = await ask("Company or organization name (optional)", "");
@@ -220,9 +454,25 @@ export async function runSetupPrompts(isUpgrade = false) {
220
454
 
221
455
  config.installDir = await ask(
222
456
  "ArkaOS data directory",
223
- join(homedir(), ".arkaos")
457
+ DEFAULT_INSTALL_DIR
224
458
  );
225
459
 
460
+ // ── Installation profile ──
461
+ console.log("\n ── Installation Profile ──\n");
462
+
463
+ if (profileFlag) {
464
+ config.installProfile = profileFlag;
465
+ console.log(` Profile: ${profileFlag} (from --profile)`);
466
+ } else {
467
+ config.installProfile = await askChoice(
468
+ "Which installation profile?",
469
+ PROFILE_OPTIONS.map(({ value, hint }) => ({
470
+ value,
471
+ label: `${value} — ${hint}`,
472
+ })),
473
+ );
474
+ }
475
+
226
476
  // ── Features ──
227
477
  console.log("\n ── Optional Features ──\n");
228
478
 
@@ -241,32 +491,44 @@ export async function runSetupPrompts(isUpgrade = false) {
241
491
  console.log(`
242
492
  ── Configuration Summary ──
243
493
 
244
- Language: ${config.language}
245
- Market: ${config.market || "(not set)"}
246
- Role: ${config.role}
247
- Company: ${config.company || "(not set)"}
248
- Projects dir: ${config.projectsDir}
249
- Obsidian vault: ${config.vaultPath || "(none)"}
250
- Install dir: ${config.installDir}
251
- Dashboard: ${config.installDashboard ? "Yes" : "No"}
252
- Knowledge DB: ${config.installKnowledge ? "Yes" : "No"}
253
- Transcription: ${config.installTranscription ? "Yes" : "No"}
254
- OpenAI key: ${config.openaiKey ? "configured" : "not set"}
255
- Google key: ${config.googleKey ? "configured" : "not set"}
256
- fal.ai key: ${config.falKey ? "configured" : "not set"}
494
+ ${formatSummary(config)
495
+ .split("\n")
496
+ .map((l) => ` ${l}`)
497
+ .join("\n")}
257
498
  `);
258
499
 
259
500
  const confirmed = await askYN("Proceed with this configuration?", true);
260
501
  if (!confirmed) {
261
502
  console.log("\n Installation cancelled.\n");
262
- rl.close();
503
+ rl?.close();
263
504
  process.exit(0);
264
505
  }
265
506
 
266
- rl.close();
507
+ rl?.close();
267
508
  return config;
268
509
  }
269
510
 
511
+ function formatSummary(config) {
512
+ return [
513
+ `Language: ${config.language}`,
514
+ `Market: ${config.market || "(not set)"}`,
515
+ `Role: ${config.role}`,
516
+ `Company: ${config.company || "(not set)"}`,
517
+ `Projects dir: ${config.projectsDir}`,
518
+ `Obsidian vault: ${config.vaultPath || "(none)"}`,
519
+ `Install dir: ${config.installDir}`,
520
+ `Profile: ${config.installProfile}`,
521
+ `Dashboard: ${config.installDashboard ? "Yes" : "No"}`,
522
+ `Knowledge DB: ${config.installKnowledge ? "Yes" : "No"}`,
523
+ `Transcription: ${config.installTranscription ? "Yes" : "No"}`,
524
+ `OpenAI key: ${config.openaiKey ? "configured" : "not set"}`,
525
+ `Google key: ${config.googleKey ? "configured" : "not set"}`,
526
+ `fal.ai key: ${config.falKey ? "configured" : "not set"}`,
527
+ ].join("\n");
528
+ }
529
+
270
530
  export function closePrompts() {
271
- try { rl.close(); } catch {}
531
+ // rl is only created when the readline wizard actually asked a
532
+ // question; a fancy/clack or short-circuited run leaves it null.
533
+ try { rl?.close(); } catch { /* already closed */ }
272
534
  }