@akanjs/cli 2.3.11-rc.6 → 2.3.11-rc.8

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.
package/index.js CHANGED
@@ -664,6 +664,7 @@ import {
664
664
  import { readFileSync as readFileSync3 } from "fs";
665
665
  import { copyFile, mkdir as mkdir2, readdir as readDirEntries, stat as stat2 } from "fs/promises";
666
666
  import path7 from "path";
667
+ import { pathToFileURL } from "url";
667
668
  import {
668
669
  capitalize,
669
670
  isRouteSourceFile,
@@ -1105,6 +1106,63 @@ var decreaseBuildNum = async (app) => {
1105
1106
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
1106
1107
  fs.writeFileSync(akanConfigPath, akanConfigContent);
1107
1108
  };
1109
+ // pkgs/@akanjs/devkit/firebaseMessagingSw.ts
1110
+ var FIREBASE_WEB_SDK_VERSION = "12.13.0";
1111
+ function normalizeFirebaseClientConfig(config) {
1112
+ if (!config || typeof config !== "object")
1113
+ return null;
1114
+ const value = config;
1115
+ if (typeof value.apiKey !== "string" || typeof value.projectId !== "string" || typeof value.messagingSenderId !== "string" || typeof value.appId !== "string") {
1116
+ return null;
1117
+ }
1118
+ return {
1119
+ apiKey: value.apiKey,
1120
+ ...typeof value.authDomain === "string" ? { authDomain: value.authDomain } : {},
1121
+ projectId: value.projectId,
1122
+ ...typeof value.storageBucket === "string" ? { storageBucket: value.storageBucket } : {},
1123
+ messagingSenderId: value.messagingSenderId,
1124
+ appId: value.appId
1125
+ };
1126
+ }
1127
+ function createFirebaseMessagingServiceWorker(config) {
1128
+ const configJson = JSON.stringify(config);
1129
+ return `/* Generated by Akan.js. Do not edit. */
1130
+ const firebaseConfig = ${configJson};
1131
+
1132
+ if (firebaseConfig) {
1133
+ importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-app-compat.js");
1134
+ importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-messaging-compat.js");
1135
+
1136
+ firebase.initializeApp(firebaseConfig);
1137
+ const messaging = firebase.messaging();
1138
+
1139
+ const notificationUrl = (payload) =>
1140
+ payload?.data?.url || payload?.fcmOptions?.link || payload?.notification?.click_action;
1141
+
1142
+ messaging.onBackgroundMessage((payload) => {
1143
+ const title = payload?.notification?.title || "";
1144
+ const options = {
1145
+ body: payload?.notification?.body,
1146
+ icon: payload?.notification?.icon,
1147
+ image: payload?.notification?.image,
1148
+ data: {
1149
+ url: notificationUrl(payload),
1150
+ FCM_MSG: payload,
1151
+ },
1152
+ };
1153
+ self.registration.showNotification(title, options);
1154
+ });
1155
+ }
1156
+
1157
+ self.addEventListener("notificationclick", (event) => {
1158
+ const url = event.notification?.data?.url || event.notification?.data?.FCM_MSG?.data?.url;
1159
+ event.notification?.close();
1160
+ if (!url) return;
1161
+ event.waitUntil(clients.openWindow(url));
1162
+ });
1163
+ `;
1164
+ }
1165
+
1108
1166
  // pkgs/@akanjs/devkit/getDirname.ts
1109
1167
  import path2 from "path";
1110
1168
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1390,6 +1448,7 @@ ${errorText}, ${warningText} found`];
1390
1448
 
1391
1449
  // pkgs/@akanjs/devkit/scanInfo.ts
1392
1450
  import path5 from "path";
1451
+ import { rm } from "fs/promises";
1393
1452
 
1394
1453
  // pkgs/@akanjs/devkit/dependencyScanner.ts
1395
1454
  import { builtinModules } from "module";
@@ -1711,6 +1770,7 @@ var appRootAllowedFiles = new Set([
1711
1770
  "tsconfig.json",
1712
1771
  "tsconfig.tsbuildinfo"
1713
1772
  ]);
1773
+ var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
1714
1774
  var appRootAllowedDirs = new Set([
1715
1775
  ".akan",
1716
1776
  "android",
@@ -1754,11 +1814,17 @@ var rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(
1754
1814
  var isAllowedTestFile = (filename) => testFilePattern.test(filename);
1755
1815
  var isAllowedLibRootFile = (filename) => libRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
1756
1816
  var getScanPath = (exec, relativePath) => path5.posix.join(`${exec.type}s`, exec.name, relativePath.split(path5.sep).join("/"));
1817
+ async function clearGeneratedRootCapacitorConfigs(exec) {
1818
+ if (exec.type !== "app")
1819
+ return;
1820
+ await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
1821
+ }
1757
1822
  var getModuleNameFromPath = (kind, modulePath) => {
1758
1823
  const dirname = path5.basename(modulePath);
1759
1824
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1760
1825
  };
1761
1826
  async function assertScanConvention(exec, libRoot) {
1827
+ await clearGeneratedRootCapacitorConfigs(exec);
1762
1828
  const violations = [];
1763
1829
  const addViolation = (relativePath, reason) => {
1764
1830
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
@@ -3575,6 +3641,8 @@ class AppExecutor extends SysExecutor {
3575
3641
  ...routeEnv,
3576
3642
  ...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
3577
3643
  });
3644
+ if (type === "build")
3645
+ Object.assign(process.env, env);
3578
3646
  return { env };
3579
3647
  }
3580
3648
  #publicEnv = null;
@@ -3685,8 +3753,30 @@ class AppExecutor extends SysExecutor {
3685
3753
  });
3686
3754
  if (write)
3687
3755
  await this.syncAssets(scanInfo.getScanResult().libDeps);
3756
+ if (write)
3757
+ await this.#syncFirebaseMessagingSw();
3688
3758
  return scanInfo;
3689
3759
  }
3760
+ async#syncFirebaseMessagingSw() {
3761
+ const swRelPath = "public/firebase-messaging-sw.js";
3762
+ if (await FileSys.fileExists(this.getPath(swRelPath)))
3763
+ return;
3764
+ const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
3765
+ if (!await FileSys.fileExists(envClientPath))
3766
+ return;
3767
+ let firebaseConfig = null;
3768
+ try {
3769
+ const envUrl = pathToFileURL(envClientPath);
3770
+ envUrl.searchParams.set("t", String(Date.now()));
3771
+ const envModule = await import(envUrl.href);
3772
+ firebaseConfig = normalizeFirebaseClientConfig(envModule.env?.firebase);
3773
+ } catch {
3774
+ return;
3775
+ }
3776
+ if (!firebaseConfig)
3777
+ return;
3778
+ await this.writeFile(swRelPath, createFirebaseMessagingServiceWorker(firebaseConfig), { overwrite: false });
3779
+ }
3690
3780
  async increaseBuildNum() {
3691
3781
  await increaseBuildNum(this);
3692
3782
  }
@@ -4994,7 +5084,6 @@ var workflowSyncStatePath = (target) => `${workflowSyncDir}/${syncStateSlug(targ
4994
5084
  var generatedFilePathsForTarget = (targetRoot, reason = "Generated files were refreshed by sync.") => [
4995
5085
  { path: `${targetRoot}/lib/cnst.ts`, action: "sync", reason },
4996
5086
  { path: `${targetRoot}/lib/dict.ts`, action: "sync", reason },
4997
- { path: `${targetRoot}/lib/option.ts`, action: "sync", reason },
4998
5087
  { path: `${targetRoot}/lib/index.ts`, action: "sync", reason }
4999
5088
  ];
5000
5089
  var writeGeneratedSyncState = async (workspace, state) => {
@@ -7648,13 +7737,59 @@ var resourceList = [
7648
7737
  { uri: "akan://workspace/modules", name: "Workspace modules", mimeType: "application/json" }
7649
7738
  ];
7650
7739
  var cursorMcpConfigPath = ".cursor/mcp.json";
7740
+ var claudeMcpConfigPath = ".mcp.json";
7741
+ var codexMcpConfigPath = ".codex/config.toml";
7742
+ var akanMcpInstallTargets = ["cursor", "claude", "codex"];
7743
+ var akanMcpInstallConfigPaths = {
7744
+ cursor: cursorMcpConfigPath,
7745
+ claude: claudeMcpConfigPath,
7746
+ codex: codexMcpConfigPath
7747
+ };
7651
7748
  var cursorWorkspaceFolder = "$" + "{workspaceFolder}";
7749
+ var claudeProjectDir = "$CLAUDE_PROJECT_DIR";
7750
+ var akanMcpCommand = (mode, { cd } = {}) => cd ? `cd "${cd}" && akan mcp --mode ${mode}` : `akan mcp --mode ${mode}`;
7652
7751
  var createAkanCursorMcpServer = (mode = "readonly") => ({
7653
7752
  type: "stdio",
7654
7753
  command: "bash",
7655
- args: ["-lc", `cd "${cursorWorkspaceFolder}" && akan mcp --mode ${mode}`]
7754
+ args: ["-lc", akanMcpCommand(mode, { cd: cursorWorkspaceFolder })]
7755
+ });
7756
+ var createAkanClaudeMcpServer = (mode = "readonly") => ({
7757
+ type: "stdio",
7758
+ command: "bash",
7759
+ args: ["-lc", akanMcpCommand(mode, { cd: claudeProjectDir })]
7656
7760
  });
7761
+ var createAkanMcpServer = (target, mode = "readonly") => target === "cursor" ? createAkanCursorMcpServer(mode) : createAkanClaudeMcpServer(mode);
7657
7762
  var akanCursorMcpServer = createAkanCursorMcpServer();
7763
+ var codexMcpServerTableHeader = "[mcp_servers.akan]";
7764
+ var createAkanCodexMcpServerBlock = (mode = "readonly") => `${codexMcpServerTableHeader}
7765
+ command = "bash"
7766
+ args = ["-lc", "${akanMcpCommand(mode)}"]
7767
+ `;
7768
+ var codexAkanTablePattern = /^\[mcp_servers\.akan\][^\n]*\n(?:(?!\[)[^\n]*(?:\n|$))*/m;
7769
+ var upsertCodexMcpServerBlock = (existing, block, { force = false } = {}) => {
7770
+ const nextBlock = block.endsWith(`
7771
+ `) ? block : `${block}
7772
+ `;
7773
+ const match = existing.match(codexAkanTablePattern);
7774
+ if (!match) {
7775
+ if (!existing.trim())
7776
+ return nextBlock;
7777
+ return `${existing.replace(/\s*$/, "")}
7778
+
7779
+ ${nextBlock}`;
7780
+ }
7781
+ if (match[0].trim() === nextBlock.trim())
7782
+ return existing;
7783
+ if (!force)
7784
+ throw new Error(`${codexMcpConfigPath} already has an "akan" MCP server. Re-run with --force to overwrite it.`);
7785
+ const start = match.index ?? 0;
7786
+ const before = existing.slice(0, start);
7787
+ const after = existing.slice(start + match[0].length);
7788
+ const separator = after && !after.startsWith(`
7789
+ `) ? `
7790
+ ` : "";
7791
+ return `${before}${nextBlock}${separator}${after}`;
7792
+ };
7658
7793
  var renderDoctorText = (result) => {
7659
7794
  const lines = [`Akan doctor status: ${result.status}`];
7660
7795
  if (result.diagnostics.length === 0) {
@@ -7677,7 +7812,6 @@ var generatedFiles = [
7677
7812
  "*/lib/cnst.ts",
7678
7813
  "*/lib/db.ts",
7679
7814
  "*/lib/dict.ts",
7680
- "*/lib/option.ts",
7681
7815
  "*/lib/sig.ts",
7682
7816
  "*/lib/srv.ts",
7683
7817
  "*/lib/st.ts",
@@ -8671,7 +8805,7 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
8671
8805
  }
8672
8806
  }
8673
8807
  // pkgs/@akanjs/devkit/applicationBuildRunner.ts
8674
- import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
8808
+ import { mkdir as mkdir8, rm as rm4 } from "fs/promises";
8675
8809
  import path37 from "path";
8676
8810
 
8677
8811
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
@@ -10429,7 +10563,7 @@ class AllRoutesBuilder {
10429
10563
  }
10430
10564
  }
10431
10565
  // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
10432
- import { mkdir as mkdir5, rm, unlink } from "fs/promises";
10566
+ import { mkdir as mkdir5, rm as rm2, unlink } from "fs/promises";
10433
10567
  import path24 from "path";
10434
10568
 
10435
10569
  // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
@@ -10559,7 +10693,7 @@ class CsrArtifactBuilder {
10559
10693
  const artifact = await this.#loadCsrArtifact();
10560
10694
  const csrBasePaths = [...akanConfig2.basePaths];
10561
10695
  const htmlEntries = csrBasePaths.length > 0 ? csrBasePaths : ["index"];
10562
- await rm(this.#outputDir, { recursive: true, force: true });
10696
+ await rm2(this.#outputDir, { recursive: true, force: true });
10563
10697
  await mkdir5(path24.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
10564
10698
  const generatedHtmlFiles = Object.fromEntries(htmlEntries.map((basePath2) => this.#createHtmlFile(basePath2)));
10565
10699
  const result = await Bun.build({
@@ -11293,7 +11427,7 @@ class DevChangePlanner {
11293
11427
  }
11294
11428
  var uniqueResolved = (files) => [...new Set(files.map((file) => path27.resolve(file)))].sort();
11295
11429
  // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
11296
- import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm2, stat as stat3, writeFile } from "fs/promises";
11430
+ import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm3, stat as stat3, writeFile } from "fs/promises";
11297
11431
  import path28 from "path";
11298
11432
  var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
11299
11433
  var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
@@ -11376,7 +11510,7 @@ class DevGeneratedIndexSync {
11376
11510
  if (content === null) {
11377
11511
  if (!await exists(indexPath))
11378
11512
  return false;
11379
- await rm2(indexPath, { force: true });
11513
+ await rm3(indexPath, { force: true });
11380
11514
  return true;
11381
11515
  }
11382
11516
  const current = await readFile(indexPath, "utf8").catch(() => null);
@@ -12523,7 +12657,7 @@ class ApplicationBuildRunner {
12523
12657
  await this.#app.getPageKeys({ refresh: true });
12524
12658
  const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig({ incremental });
12525
12659
  if (clean)
12526
- await rm3(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12660
+ await rm4(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12527
12661
  await this.#checkProjectInChildProcess(tsconfigPath);
12528
12662
  }
12529
12663
  async#runPhase(id, label, task, summarize, options = {}) {
@@ -12723,7 +12857,7 @@ void run().catch((error) => {
12723
12857
  }
12724
12858
  }
12725
12859
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
12726
- import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
12860
+ import { cp, mkdir as mkdir9, rm as rm5 } from "fs/promises";
12727
12861
  import path38 from "path";
12728
12862
 
12729
12863
  // pkgs/@akanjs/devkit/uploadRelease.ts
@@ -12830,7 +12964,7 @@ class ApplicationReleasePackager {
12830
12964
  const platformVersion = akanConfig2.mobile.version;
12831
12965
  const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
12832
12966
  if (await FileSys.dirExists(buildRoot))
12833
- await rm4(buildRoot, { recursive: true, force: true });
12967
+ await rm5(buildRoot, { recursive: true, force: true });
12834
12968
  await mkdir9(buildRoot, { recursive: true });
12835
12969
  if (rebuild || !await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))
12836
12970
  await this.#build();
@@ -12839,7 +12973,7 @@ class ApplicationReleasePackager {
12839
12973
  await mkdir9(buildPath, { recursive: true });
12840
12974
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
12841
12975
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
12842
- await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12976
+ await rm5(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12843
12977
  const releaseRoot = this.#app.workspace.workspaceRoot;
12844
12978
  const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
12845
12979
  await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
@@ -12855,7 +12989,7 @@ class ApplicationReleasePackager {
12855
12989
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
12856
12990
  "./csr"
12857
12991
  ]);
12858
- await rm4("./csr", { recursive: true, force: true });
12992
+ await rm5("./csr", { recursive: true, force: true });
12859
12993
  }
12860
12994
  async#writeSourceArchive({ readme }) {
12861
12995
  const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
@@ -12866,7 +13000,7 @@ class ApplicationReleasePackager {
12866
13000
  await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
12867
13001
  const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
12868
13002
  if (await FileSys.dirExists(targetPath))
12869
- await rm4(targetPath, { recursive: true, force: true });
13003
+ await rm5(targetPath, { recursive: true, force: true });
12870
13004
  }));
12871
13005
  const syncPaths = [".husky", ".gitignore", "package.json"];
12872
13006
  await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
@@ -12881,7 +13015,7 @@ class ApplicationReleasePackager {
12881
13015
  const maxRetry = 3;
12882
13016
  for (let i = 0;i < maxRetry; i++) {
12883
13017
  try {
12884
- await rm4(sourceRoot, { recursive: true, force: true });
13018
+ await rm5(sourceRoot, { recursive: true, force: true });
12885
13019
  } catch {}
12886
13020
  }
12887
13021
  }
@@ -13114,7 +13248,7 @@ class Builder {
13114
13248
  }
13115
13249
  }
13116
13250
  // pkgs/@akanjs/devkit/capacitorApp.ts
13117
- import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm5, writeFile as writeFile2 } from "fs/promises";
13251
+ import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm6, writeFile as writeFile2 } from "fs/promises";
13118
13252
  import os from "os";
13119
13253
  import path41 from "path";
13120
13254
  import { select as select2 } from "@inquirer/prompts";
@@ -13325,7 +13459,7 @@ var rootCapacitorConfigFilenames = [
13325
13459
  ];
13326
13460
  var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
13327
13461
  async function clearRootCapacitorConfigs(appRoot) {
13328
- await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
13462
+ await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm6(file, { force: true })));
13329
13463
  }
13330
13464
  async function writeRootCapacitorConfig(appRoot, content) {
13331
13465
  await clearRootCapacitorConfigs(appRoot);
@@ -13346,6 +13480,7 @@ var getLocalIP = () => {
13346
13480
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13347
13481
  var asString = (value) => typeof value === "string" ? value : undefined;
13348
13482
  var firstString = (...values) => values.find((value) => typeof value === "string");
13483
+ var resolveIosApnsEnvironment = ({ operation }) => operation === "release" ? "production" : "development";
13349
13484
  var scoreIosDeviceTarget = (target) => {
13350
13485
  const state = target.state?.toLowerCase() ?? "";
13351
13486
  return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
@@ -13651,6 +13786,8 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13651
13786
  const experimentalConfig = isRecord2(experimental) ? experimental : undefined;
13652
13787
  const pluginsConfig = isRecord2(plugins) ? plugins : {};
13653
13788
  const keyboardPluginConfig = isRecord2(pluginsConfig.Keyboard) ? pluginsConfig.Keyboard : {};
13789
+ const pushNotificationsPluginConfig = isRecord2(pluginsConfig.PushNotifications) ? pluginsConfig.PushNotifications : {};
13790
+ const usesPushNotifications = target.permissions?.includes("push") ?? false;
13654
13791
  const config = {
13655
13792
  ...capacitorConfig,
13656
13793
  appId,
@@ -13659,6 +13796,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13659
13796
  plugins: {
13660
13797
  CapacitorCookies: { enabled: true },
13661
13798
  ...pluginsConfig,
13799
+ ...usesPushNotifications || isRecord2(pluginsConfig.PushNotifications) ? {
13800
+ PushNotifications: {
13801
+ ...usesPushNotifications ? { presentationOptions: ["badge", "sound", "alert"] } : {},
13802
+ ...pushNotificationsPluginConfig
13803
+ }
13804
+ } : {},
13662
13805
  Keyboard: {
13663
13806
  resize: "none",
13664
13807
  ...keyboardPluginConfig
@@ -13730,9 +13873,9 @@ class CapacitorApp {
13730
13873
  await mkdir11(this.targetRoot, { recursive: true });
13731
13874
  if (regenerate) {
13732
13875
  if (!platform || platform === "ios")
13733
- await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13876
+ await rm6(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13734
13877
  if (!platform || platform === "android")
13735
- await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13878
+ await rm6(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13736
13879
  }
13737
13880
  const project = this.project;
13738
13881
  await this.project.load();
@@ -13754,7 +13897,7 @@ class CapacitorApp {
13754
13897
  await this.#prepareTargetAssets();
13755
13898
  await this.#prepareExternalFiles("ios");
13756
13899
  await this.#applyIosMetadata();
13757
- await this.#applyPermissions();
13900
+ await this.#applyPermissions({ operation, env });
13758
13901
  await this.#applyDeepLinks("ios", { operation, env });
13759
13902
  await this.project.commit();
13760
13903
  await this.#generateAssets({ operation, env });
@@ -13897,7 +14040,7 @@ ${error.message}`;
13897
14040
  await this.#prepareTargetAssets();
13898
14041
  await this.#prepareExternalFiles("android");
13899
14042
  await this.#applyAndroidMetadata();
13900
- await this.#applyPermissions();
14043
+ await this.#applyPermissions({ operation, env });
13901
14044
  await this.#applyDeepLinks("android", { operation, env });
13902
14045
  await this.project.commit();
13903
14046
  await this.#generateAssets({ operation, env });
@@ -14031,7 +14174,7 @@ ${error.message}`;
14031
14174
  const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
14032
14175
  if (!await Bun.file(htmlSource).exists())
14033
14176
  throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
14034
- await rm5(this.targetWebRoot, { recursive: true, force: true });
14177
+ await rm6(this.targetWebRoot, { recursive: true, force: true });
14035
14178
  await mkdir11(this.targetWebRoot, { recursive: true });
14036
14179
  await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
14037
14180
  }
@@ -14112,7 +14255,7 @@ ${error.message}`;
14112
14255
  await this.project.android.setVersionCode(this.target.buildNum);
14113
14256
  await this.project.android.setAppName(this.target.appName);
14114
14257
  }
14115
- async#applyPermissions() {
14258
+ async#applyPermissions({ operation, env }) {
14116
14259
  for (const permission of this.target.permissions ?? []) {
14117
14260
  if (permission === "camera")
14118
14261
  await this.addCamera();
@@ -14121,7 +14264,7 @@ ${error.message}`;
14121
14264
  else if (permission === "location")
14122
14265
  await this.addLocation();
14123
14266
  else if (permission === "push")
14124
- await this.addPush();
14267
+ await this.addPush({ operation, env });
14125
14268
  }
14126
14269
  }
14127
14270
  async#applyDeepLinks(platform, { operation, env }) {
@@ -14140,7 +14283,7 @@ ${error.message}`;
14140
14283
  await this.#setUrlSchemesInIos(schemes);
14141
14284
  }
14142
14285
  if (domains.length > 0)
14143
- await this.#setAssociatedDomainsInIos(domains);
14286
+ await this.#setAssociatedDomainsInIos(domains, { operation, env });
14144
14287
  return;
14145
14288
  }
14146
14289
  if (platform === "android") {
@@ -14234,12 +14377,64 @@ ${error.message}`;
14234
14377
  this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
14235
14378
  this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
14236
14379
  }
14237
- async addPush() {
14380
+ async addPush({ operation, env }) {
14238
14381
  await this.#setPermissionInIos({
14239
14382
  userNotificationsUsageDescription: "$(PRODUCT_NAME) uses notifications to keep you updated."
14240
14383
  });
14384
+ await Promise.all([
14385
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { UIBackgroundModes: ["remote-notification"] }),
14386
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { UIBackgroundModes: ["remote-notification"] }),
14387
+ this.#writeEntitlementsInIos(this.target.deepLinks?.domains ?? [], { operation, env }),
14388
+ this.#ensurePushAppDelegateInIos()
14389
+ ]);
14241
14390
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
14242
14391
  }
14392
+ async#ensurePushAppDelegateInIos() {
14393
+ const appDelegatePath = path41.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
14394
+ if (!await Bun.file(appDelegatePath).exists())
14395
+ return;
14396
+ const editor = await FileEditor.create(appDelegatePath);
14397
+ let content = editor.getContent();
14398
+ if (!content.includes("import FirebaseCore")) {
14399
+ content = content.replace("import Capacitor", `import Capacitor
14400
+ import FirebaseCore`);
14401
+ }
14402
+ if (!content.includes("import FirebaseMessaging")) {
14403
+ content = content.replace("import FirebaseCore", `import FirebaseCore
14404
+ import FirebaseMessaging`);
14405
+ }
14406
+ if (!content.includes("FirebaseApp.configure()")) {
14407
+ content = content.replace(/\n(\s*)return true/, `
14408
+ $1FirebaseApp.configure()
14409
+
14410
+ $1return true`);
14411
+ }
14412
+ if (!content.includes("didRegisterForRemoteNotificationsWithDeviceToken")) {
14413
+ const delegateMethods = `
14414
+ func application(_ application: UIApplication,
14415
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
14416
+ Messaging.messaging().apnsToken = deviceToken
14417
+ NotificationCenter.default.post(
14418
+ name: .capacitorDidRegisterForRemoteNotifications,
14419
+ object: deviceToken
14420
+ )
14421
+ }
14422
+
14423
+ func application(_ application: UIApplication,
14424
+ didFailToRegisterForRemoteNotificationsWithError error: Error) {
14425
+ NotificationCenter.default.post(
14426
+ name: .capacitorDidFailToRegisterForRemoteNotifications,
14427
+ object: error
14428
+ )
14429
+ }
14430
+ `;
14431
+ content = content.replace(`
14432
+ func applicationWillResignActive`, `${delegateMethods}
14433
+ func applicationWillResignActive`);
14434
+ }
14435
+ if (content !== editor.getContent())
14436
+ await editor.setContent(content).save();
14437
+ }
14243
14438
  async#setPermissionInIos(permissions) {
14244
14439
  const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
14245
14440
  await Promise.all([
@@ -14257,25 +14452,35 @@ ${error.message}`;
14257
14452
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
14258
14453
  ]);
14259
14454
  }
14260
- async#setAssociatedDomainsInIos(domains) {
14455
+ async#setAssociatedDomainsInIos(domains, { operation, env }) {
14456
+ await this.#writeEntitlementsInIos(domains, { operation, env });
14457
+ }
14458
+ async#writeEntitlementsInIos(domains, runConfig) {
14261
14459
  const entitlementsRelPath = "App/App.entitlements";
14262
14460
  const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
14263
14461
  const values = domains.map((domain) => `applinks:${domain}`);
14462
+ const usesPush = this.target.permissions?.includes("push") ?? false;
14463
+ const apsEnvironment = resolveIosApnsEnvironment(runConfig);
14264
14464
  const body = [
14265
14465
  '<?xml version="1.0" encoding="UTF-8"?>',
14266
14466
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
14267
14467
  '<plist version="1.0">',
14268
14468
  "<dict>",
14269
- " <key>com.apple.developer.associated-domains</key>",
14270
- " <array>",
14271
- ...values.map((value) => ` <string>${value}</string>`),
14272
- " </array>",
14469
+ ...usesPush ? [" <key>aps-environment</key>", ` <string>${apsEnvironment}</string>`] : [],
14470
+ ...values.length > 0 ? [
14471
+ " <key>com.apple.developer.associated-domains</key>",
14472
+ " <array>",
14473
+ ...values.map((value) => ` <string>${value}</string>`),
14474
+ " </array>"
14475
+ ] : [],
14273
14476
  "</dict>",
14274
14477
  "</plist>",
14275
14478
  ""
14276
14479
  ].join(`
14277
14480
  `);
14278
- await writeFile2(entitlementsPath, body);
14481
+ const currentBody = await Bun.file(entitlementsPath).exists() ? await Bun.file(entitlementsPath).text() : undefined;
14482
+ if (currentBody !== body)
14483
+ await writeFile2(entitlementsPath, body);
14279
14484
  await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
14280
14485
  }
14281
14486
  async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
@@ -14379,7 +14584,7 @@ ${filter}$1`);
14379
14584
  for (const permission of permissions) {
14380
14585
  if (this.#hasPermissionInAndroid(permission)) {
14381
14586
  this.app.logger.info(`${permission} already exists in android`);
14382
- return this;
14587
+ continue;
14383
14588
  }
14384
14589
  this.app.logger.info(`Adding ${permission} to android`);
14385
14590
  this.project.android.getAndroidManifest().injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
@@ -14395,7 +14600,7 @@ ${filter}$1`);
14395
14600
  return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
14396
14601
  }
14397
14602
  #hasPermissionInAndroid(permission) {
14398
- return this.#getPermissionsInAndroid().includes(permission);
14603
+ return this.#getPermissionsInAndroid().includes(`android.permission.${permission}`);
14399
14604
  }
14400
14605
  }
14401
14606
  // pkgs/@akanjs/devkit/commandDecorators/targetMeta.ts
@@ -18092,8 +18297,7 @@ var addEnumFieldWorkflowSpec = {
18092
18297
  predictedChanges: [
18093
18298
  { target: "*/lib/<module>/<module>.constant.ts", action: "modify", reason: "Enum field shape is added." },
18094
18299
  { target: "*/lib/<module>/<module>.dictionary.ts", action: "modify", reason: "Enum labels are added." },
18095
- { target: "*/lib/<module>/<module>.option.ts", action: "modify", reason: "Enum options may be added." },
18096
- { target: "*/lib/option.ts", action: "sync", reason: "Generated option barrel may change after sync." }
18300
+ { target: "*/lib/<module>/<module>.option.ts", action: "modify", reason: "Enum options may be added." }
18097
18301
  ],
18098
18302
  validation: [
18099
18303
  ...baseValidation,
@@ -18498,8 +18702,7 @@ var createScalarWorkflowSpec = {
18498
18702
  }
18499
18703
  ],
18500
18704
  predictedChanges: [
18501
- { target: "*/lib/__scalar/<scalar>/*", action: "create", reason: "New scalar source files are scaffolded." },
18502
- { target: "*/lib/option.ts", action: "sync", reason: "Generated option barrel may include scalar options." }
18705
+ { target: "*/lib/__scalar/<scalar>/*", action: "create", reason: "New scalar source files are scaffolded." }
18503
18706
  ],
18504
18707
  validation: baseValidation,
18505
18708
  completionCriteria: ["Scalar files exist under __scalar.", "Generated files are refreshed.", "Lint passes."]
@@ -20370,14 +20573,18 @@ class ContextRunner extends runner("context") {
20370
20573
  return await Prompter.getInstruction(name);
20371
20574
  }
20372
20575
  async installMcp(workspace, target, { force = false, mode = "readonly" } = {}) {
20373
- if (target !== "cursor")
20374
- throw new Error(`Unknown MCP install target: ${target}. Use cursor.`);
20375
- const existing = await workspace.exists(cursorMcpConfigPath) ? await workspace.readJson(cursorMcpConfigPath) : {};
20576
+ if (target === "codex")
20577
+ return await this.#installCodexMcp(workspace, { force, mode });
20578
+ return await this.#installJsonMcp(workspace, target, { force, mode });
20579
+ }
20580
+ async#installJsonMcp(workspace, target, { force, mode }) {
20581
+ const configPath2 = akanMcpInstallConfigPaths[target];
20582
+ const existing = await workspace.exists(configPath2) ? await workspace.readJson(configPath2) : {};
20376
20583
  const mcpServers = existing.mcpServers ?? {};
20377
20584
  const currentAkanServer = mcpServers.akan;
20378
- const nextAkanServer = createAkanCursorMcpServer(mode);
20585
+ const nextAkanServer = createAkanMcpServer(target, mode);
20379
20586
  if (currentAkanServer && !force && JSON.stringify(currentAkanServer) !== JSON.stringify(nextAkanServer)) {
20380
- throw new Error(`${cursorMcpConfigPath} already has an "akan" MCP server. Re-run with --force to overwrite it.`);
20587
+ throw new Error(`${configPath2} already has an "akan" MCP server. Re-run with --force to overwrite it.`);
20381
20588
  }
20382
20589
  const nextConfig = {
20383
20590
  ...existing,
@@ -20386,9 +20593,15 @@ class ContextRunner extends runner("context") {
20386
20593
  akan: nextAkanServer
20387
20594
  }
20388
20595
  };
20389
- await workspace.writeFile(cursorMcpConfigPath, `${JSON.stringify(nextConfig, null, 2)}
20596
+ await workspace.writeFile(configPath2, `${JSON.stringify(nextConfig, null, 2)}
20390
20597
  `);
20391
- return cursorMcpConfigPath;
20598
+ return configPath2;
20599
+ }
20600
+ async#installCodexMcp(workspace, { force, mode }) {
20601
+ const existing = await workspace.exists(codexMcpConfigPath) ? await workspace.readFile(codexMcpConfigPath) : "";
20602
+ const merged = upsertCodexMcpServerBlock(existing, createAkanCodexMcpServerBlock(mode), { force });
20603
+ await workspace.writeFile(codexMcpConfigPath, merged);
20604
+ return codexMcpConfigPath;
20392
20605
  }
20393
20606
  listMcpTools(mode = "readonly") {
20394
20607
  return listAkanMcpTools(mode);
@@ -20680,13 +20893,26 @@ ${payload}`);
20680
20893
  agent: "`akan agent install <target>` writes editor-specific agent rules with overwrite protection.",
20681
20894
  mcp: "`akan mcp --mode readonly|plan|apply` starts the Akan MCP server over stdio with an explicit permission mode.",
20682
20895
  "mcp-call": '`akan mcp-call <tool> --mode plan --args \'{"workflow":"add-field","inputs":{"app":"demo"}}\'` calls one MCP tool through the same runner path for debugging.',
20683
- "mcp-install": "`akan mcp-install cursor --mode readonly|plan|apply` installs the Akan MCP server config for Cursor."
20896
+ "mcp-install": "`akan mcp-install [cursor|claude|codex|all] --mode readonly|plan|apply` installs the Akan MCP server config for Cursor (.cursor/mcp.json), Claude Code (.mcp.json), and Codex (.codex/config.toml). Defaults to all targets."
20684
20897
  };
20685
20898
  return explanations[command3] ?? `No detailed explanation is available for ${command3}. Run \`akan --help\` for command help.`;
20686
20899
  }
20687
20900
  }
20688
20901
 
20689
20902
  // pkgs/@akanjs/cli/context/context.script.ts
20903
+ var resolveMcpInstallTargets = (target) => {
20904
+ if (!target || target === "all")
20905
+ return [...akanMcpInstallTargets];
20906
+ if (akanMcpInstallTargets.includes(target))
20907
+ return [target];
20908
+ throw new Error(`Unknown MCP install target: ${target}. Use cursor, claude, codex, or all.`);
20909
+ };
20910
+ var mcpTargetLabels = {
20911
+ cursor: "Cursor",
20912
+ claude: "Claude Code",
20913
+ codex: "Codex"
20914
+ };
20915
+
20690
20916
  class ContextScript extends script("context", [ContextRunner]) {
20691
20917
  async context(workspace, options = {}) {
20692
20918
  Logger17.rawLog(await this.contextRunner.getContext(workspace, options));
@@ -20695,11 +20921,15 @@ class ContextScript extends script("context", [ContextRunner]) {
20695
20921
  Logger17.rawLog(await this.contextRunner.doctor(workspace, options));
20696
20922
  }
20697
20923
  async mcpInstall(workspace, target, { force = false, mode = "readonly" } = {}) {
20698
- if (target && target !== "cursor")
20699
- throw new Error(`Unknown MCP install target: ${target}. Use cursor.`);
20700
- const written = await this.contextRunner.installMcp(workspace, "cursor", { force, mode });
20701
- Logger17.rawLog(`Akan MCP server installed for Cursor:
20702
- - ${written}`);
20924
+ const targets = resolveMcpInstallTargets(target);
20925
+ const written = [];
20926
+ for (const t of targets) {
20927
+ const configPath2 = await this.contextRunner.installMcp(workspace, t, { force, mode });
20928
+ written.push(`${mcpTargetLabels[t]}: ${configPath2}`);
20929
+ }
20930
+ Logger17.rawLog(`Akan MCP server installed (${mode} mode):
20931
+ ${written.map((line) => `- ${line}`).join(`
20932
+ `)}`);
20703
20933
  }
20704
20934
  async mcp(workspace, { mode = "readonly" } = {}) {
20705
20935
  await this.contextRunner.runMcp(workspace, { mode });
@@ -20738,7 +20968,7 @@ class ContextCommand extends command("context", [ContextScript], ({ public: targ
20738
20968
  }).option("strict", Boolean, { desc: "treat recommended conventions as errors", default: false }).with(Workspace).exec(async function(format, strict, workspace) {
20739
20969
  await this.contextScript.doctor(workspace, { format, strict });
20740
20970
  }),
20741
- mcpInstall: target({ desc: "Install the Akan MCP server config for Cursor" }).arg("target", String, { desc: "cursor", nullable: true }).option("force", Boolean, { desc: "overwrite an existing Akan MCP server entry", default: false }).option("mode", String, {
20971
+ mcpInstall: target({ desc: "Install the Akan MCP server config for Cursor, Claude Code, and Codex" }).arg("target", String, { desc: "cursor, claude, codex, or all", nullable: true }).option("force", Boolean, { desc: "overwrite an existing Akan MCP server entry", default: false }).option("mode", String, {
20742
20972
  desc: "MCP permission mode",
20743
20973
  default: "readonly",
20744
20974
  enum: ["readonly", "plan", "apply"]
@@ -21123,7 +21353,7 @@ class LibraryCommand extends command("library", [LibraryScript], ({ public: targ
21123
21353
  }
21124
21354
 
21125
21355
  // pkgs/@akanjs/cli/localRegistry/localRegistry.runner.ts
21126
- import { mkdir as mkdir13, rm as rm6 } from "fs/promises";
21356
+ import { mkdir as mkdir13, rm as rm7 } from "fs/promises";
21127
21357
  import path47 from "path";
21128
21358
  import { Logger as Logger19 } from "akanjs/common";
21129
21359
  var defaultLocalRegistryUrl = "http://127.0.0.1:4873";
@@ -21166,13 +21396,13 @@ class LocalRegistryRunner extends runner("localRegistry") {
21166
21396
  try {
21167
21397
  await workspace.spawn("docker", ["rm", "-f", containerName], { stdio: "inherit" });
21168
21398
  } catch {}
21169
- await rm6(path47.join(workspace.workspaceRoot, ".akan/verdaccio"), { recursive: true, force: true });
21399
+ await rm7(path47.join(workspace.workspaceRoot, ".akan/verdaccio"), { recursive: true, force: true });
21170
21400
  Logger19.info("Local registry storage has been reset");
21171
21401
  }
21172
21402
  async smoke(workspace, { registryUrl } = {}) {
21173
21403
  const registry = this.getRegistryUrl(registryUrl);
21174
21404
  const smokeRoot = path47.join(workspace.workspaceRoot, ".akan/e2e");
21175
- await rm6(path47.join(smokeRoot, smokeRepoName), { recursive: true, force: true });
21405
+ await rm7(path47.join(smokeRoot, smokeRepoName), { recursive: true, force: true });
21176
21406
  await workspace.spawn(process.execPath, [
21177
21407
  "dist/pkgs/create-akan-workspace/index.js",
21178
21408
  smokeRepoName,