@akanjs/cli 3.0.2-rc.0 → 3.1.0-rc.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.
- package/.build-stamp +1 -1
- package/{CodeAgent-szbjv4gv.js → CodeAgent-q7a4srt6.js} +2 -2
- package/{CodeTui-5nw86ab6.js → CodeTui-ak23dxc9.js} +1 -1
- package/{TunnelShare-cwh9ezsm.js → TunnelShare-21mm6dr5.js} +2 -2
- package/{agent.command-np1dffkb.js → agent.command-f3gsc1gd.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-fyjy9ryc.js → application.command-3v2d53gd.js} +38 -16
- package/{applicationBuildRunner-etrzfwjj.js → applicationBuildRunner-1yaqsrdq.js} +115 -103
- package/buildBatch.proc.js +17 -6
- package/cloud.command-kk2rwkvw.js +187 -0
- package/cloud.script-r7eyfey4.js +20 -0
- package/{code.command-hmtx14b2.js → code.command-30sca09d.js} +6 -6
- package/commandManifest.json +1 -1
- package/{context.command-6aqvdx43.js → context.command-k7eqacq6.js} +13 -13
- package/context.runner-hqd1ppqe.js +21 -0
- package/{guideline.command-axvf68gc.js → guideline.command-dtczrqpf.js} +2 -2
- package/guidelines/agentRule/agentRule.instruction.md +10 -2
- package/guidelines/conventions/conventions.instruction.md +68 -25
- package/guidelines/cssRule/cssRule.instruction.md +19 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +67 -12
- package/guidelines/queryRule/queryRule.instruction.md +11 -1
- package/guidelines/runtimeRule/runtimeRule.instruction.md +97 -16
- package/guidelines/ssrRule/ssrRule.instruction.md +10 -7
- package/guidelines/transportRule/transportRule.instruction.md +19 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +6 -6
- package/incrementalBuilder.proc.js +25 -4
- package/{index-h9ht6s60.js → index-05g3jayg.js} +1 -1
- package/{index-j38qkvkk.js → index-0bm04n5g.js} +12 -10
- package/{index-g38nhv87.js → index-2etcv0sc.js} +1 -1
- package/{index-zyw622y1.js → index-2mx692qq.js} +3 -3
- package/{index-jb0hmt3k.js → index-2t4tqd1c.js} +3 -3
- package/{index-50wb2a26.js → index-55mynnqs.js} +354 -129
- package/{index-q6ft9fkc.js → index-6jjtnhkw.js} +8 -3
- package/index-6w0rbr9d.js +68 -0
- package/{index-2wc41k5w.js → index-7bymavqk.js} +2 -2
- package/{index-krjcg4y6.js → index-aprt7pkz.js} +7 -10
- package/{index-1bjkfvm9.js → index-c1x0be5j.js} +2 -2
- package/{index-d95tfg3n.js → index-c2yrsxdh.js} +14 -8
- package/{index-cb6yc5mb.js → index-c76k723e.js} +2 -2
- package/{index-6keyf4sg.js → index-ctanya41.js} +4 -4
- package/{index-f4e08ptp.js → index-e0cvze1s.js} +4 -4
- package/{index-hp3stk1m.js → index-fm46xrf1.js} +3 -3
- package/{index-3xmzr1p1.js → index-gnyf1fqy.js} +2 -2
- package/{index-zwd84m0w.js → index-jyv387qw.js} +4 -4
- package/{index-senscydg.js → index-k9rrten6.js} +6 -1
- package/{index-jk9xnck6.js → index-kjvrq3nc.js} +231 -133
- package/{index-xwprn9nr.js → index-nz15k0b6.js} +1 -1
- package/{index-61mmsdjg.js → index-p9ystjrp.js} +174 -72
- package/index-q958tqqg.js +77 -0
- package/{index-50jjwg8y.js → index-qt22xstj.js} +1 -1
- package/{index-94s080qm.js → index-r2tpeb3n.js} +127 -1
- package/{index-bsv901fd.js → index-rwq3ekqc.js} +7 -7
- package/{index-gvpvxdm1.js → index-tn4rpqp1.js} +96 -12
- package/{index-1gs9g12n.js → index-v29746vb.js} +4 -4
- package/index-ww9bhp67.js +62 -0
- package/{index-hxnnfy57.js → index-y8q31011.js} +180 -8
- package/index.js +22 -21
- package/{library.command-9m2rdwwz.js → library.command-gaga5bmq.js} +3 -3
- package/{localRegistry.command-vacc2njh.js → localRegistry.command-9e84e32q.js} +10 -10
- package/{module.command-q63dffya.js → module.command-7vpf6db4.js} +5 -5
- package/{package.command-y2jh0m99.js → package.command-twmvvf69.js} +3 -3
- package/package.json +2 -2
- package/{page.command-f3e4cwhh.js → page.command-5asq7fdf.js} +3 -3
- package/pipeline.command-d0v38572.js +1334 -0
- package/{primitive.command-r4kq75qj.js → primitive.command-dyrs2fm8.js} +6 -6
- package/projectTypecheck-cvtkrzp2.js +7 -0
- package/{quality.command-f7gzh26z.js → quality.command-6emsqpbg.js} +5 -4
- package/{repair.command-0qmyr6qa.js → repair.command-8t6aegv5.js} +5 -5
- package/{scalar.command-kddn7av4.js → scalar.command-bnggy1x7.js} +4 -4
- package/{subspace.command-j7ayp382.js → subspace.command-f02j5tge.js} +158 -91
- package/templates/app/tsconfig.json.template +1 -6
- package/templates/appSample/page/task/[taskId]/_index.tsx +2 -2
- package/templates/appSample/page/task/[taskId]/edit.tsx +2 -2
- package/templates/appSample/page/task/_index.tsx +2 -2
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -2
- package/templates/crudPages/[__model__Id]/page.tsx +3 -3
- package/templates/crudPages/page.tsx +2 -2
- package/templates/crudSinglePage/page.tsx +3 -3
- package/templates/lib/cnst.ts +2 -2
- package/templates/lib/sig.ts +3 -3
- package/{tunnel.command-b3ymdk4f.js → tunnel.command-9x8nfn75.js} +16 -13
- package/{workflow.command-z1srcdfw.js → workflow.command-jx0wag5r.js} +10 -10
- package/{workspace.command-17c46dbp.js → workspace.command-cmj6mqyk.js} +58 -28
- package/cloud.command-c0rnwy65.js +0 -95
- package/cloud.script-6ba8e9v8.js +0 -20
- package/context.runner-hnkvv3z3.js +0 -21
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
App,
|
|
4
|
+
Workspace,
|
|
5
|
+
command
|
|
6
|
+
} from "./index-6jjtnhkw.js";
|
|
7
|
+
import"./index-52w0fvh8.js";
|
|
8
|
+
import"./index-55mynnqs.js";
|
|
9
|
+
import"./index-kjvrq3nc.js";
|
|
10
|
+
import"./index-r2tpeb3n.js";
|
|
11
|
+
import {
|
|
12
|
+
PlatformTestRun
|
|
13
|
+
} from "./index-y8q31011.js";
|
|
14
|
+
import"./index-k9rrten6.js";
|
|
15
|
+
import"./index-c1x0be5j.js";
|
|
16
|
+
import"./index-p9ystjrp.js";
|
|
17
|
+
import"./index-7md2g9vw.js";
|
|
18
|
+
import"./index-2ahj896a.js";
|
|
19
|
+
import"./index-gnyf1fqy.js";
|
|
20
|
+
import {
|
|
21
|
+
CloudScript2
|
|
22
|
+
} from "./index-tn4rpqp1.js";
|
|
23
|
+
import"./index-qt22xstj.js";
|
|
24
|
+
import {
|
|
25
|
+
getMobileTargetChoices
|
|
26
|
+
} from "./index-q958tqqg.js";
|
|
27
|
+
|
|
28
|
+
// pkgs/@akanjs/cli/cloud/cloud.command.ts
|
|
29
|
+
var localRegistryUrl = () => process.env.AKAN_NPM_REGISTRY ?? "http://127.0.0.1:4873";
|
|
30
|
+
var hostOption = {
|
|
31
|
+
desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
32
|
+
nullable: true
|
|
33
|
+
};
|
|
34
|
+
var targetOption = {
|
|
35
|
+
desc: "native target name (a key of native.targets)",
|
|
36
|
+
ask: "Select native target",
|
|
37
|
+
enum: async ({ app }) => await getMobileTargetChoices(app)
|
|
38
|
+
};
|
|
39
|
+
var envOption = {
|
|
40
|
+
enum: ["debug", "develop", "main", "local"],
|
|
41
|
+
desc: "backend environment",
|
|
42
|
+
default: "main"
|
|
43
|
+
};
|
|
44
|
+
var resolveRegistryUrl = (registry) => registry === "local" ? localRegistryUrl() : undefined;
|
|
45
|
+
|
|
46
|
+
class CloudCommand extends command("cloud", [CloudScript2], ({ public: target }) => ({
|
|
47
|
+
login: target({ desc: "Login to Akan Cloud services" }).option("host", String, {
|
|
48
|
+
desc: "host of the cloud (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
49
|
+
nullable: true
|
|
50
|
+
}).with(Workspace).exec(async function(host, workspace) {
|
|
51
|
+
await this.cloudScript.login(workspace, host);
|
|
52
|
+
}),
|
|
53
|
+
logout: target({ desc: "Logout from Akan Cloud services" }).option("host", String, {
|
|
54
|
+
desc: "host of the cloud (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
55
|
+
nullable: true
|
|
56
|
+
}).with(Workspace).exec(async function(host, workspace) {
|
|
57
|
+
await this.cloudScript.logout(workspace, host);
|
|
58
|
+
}),
|
|
59
|
+
deployAkan: target({ devOnly: true, desc: "Deploy Akan.js framework to cloud (internal use)" }).option("test", Boolean, { desc: "test the deployment", default: true }).option("registry", String, {
|
|
60
|
+
desc: "registry target for publishing Akan packages",
|
|
61
|
+
ask: "Select a registry target",
|
|
62
|
+
enum: [
|
|
63
|
+
{ label: "local", value: "local" },
|
|
64
|
+
{ label: "npm", value: "npm" }
|
|
65
|
+
]
|
|
66
|
+
}).option("platforms", String, {
|
|
67
|
+
desc: "platforms to test on besides this machine, comma-separated: linux, windows (none to skip)",
|
|
68
|
+
default: "linux,windows"
|
|
69
|
+
}).with(Workspace).exec(async function(test, registry, platforms, workspace) {
|
|
70
|
+
await this.cloudScript.deployAkan(workspace, {
|
|
71
|
+
test,
|
|
72
|
+
registryUrl: resolveRegistryUrl(registry),
|
|
73
|
+
platforms: PlatformTestRun.parsePlatforms(platforms)
|
|
74
|
+
});
|
|
75
|
+
}),
|
|
76
|
+
testPlatforms: target({
|
|
77
|
+
devOnly: true,
|
|
78
|
+
desc: "Run the Akan package suites on Linux (Docker) and Windows (SSH) against a snapshot of this tree"
|
|
79
|
+
}).option("platforms", String, {
|
|
80
|
+
desc: "platforms to test on, comma-separated: linux, windows",
|
|
81
|
+
default: "linux,windows"
|
|
82
|
+
}).option("pkgs", String, {
|
|
83
|
+
flag: "k",
|
|
84
|
+
desc: "packages to test, comma-separated (default: every Akan package)",
|
|
85
|
+
nullable: true
|
|
86
|
+
}).with(Workspace).exec(async function(platforms, pkgs, workspace) {
|
|
87
|
+
await this.cloudScript.testPlatforms(workspace, {
|
|
88
|
+
platforms: PlatformTestRun.parsePlatforms(platforms),
|
|
89
|
+
pkgs: pkgs?.split(",").map((pkg) => pkg.trim()).filter(Boolean)
|
|
90
|
+
});
|
|
91
|
+
}),
|
|
92
|
+
update: target({ desc: "Update Akan.js framework to the latest version" }).with(Workspace).option("tag", String, {
|
|
93
|
+
desc: "tag of the update",
|
|
94
|
+
default: "latest",
|
|
95
|
+
enum: ["latest", "dev", "canary", "beta", "rc", "alpha"]
|
|
96
|
+
}).option("registry", String, {
|
|
97
|
+
desc: "registry target for resolving Akan packages",
|
|
98
|
+
ask: "Select a registry target",
|
|
99
|
+
enum: [
|
|
100
|
+
{ label: "npm", value: "npm" },
|
|
101
|
+
{ label: "local", value: "local" }
|
|
102
|
+
],
|
|
103
|
+
default: process.env.USE_AKANJS_PKGS === "true" ? undefined : "npm"
|
|
104
|
+
}).exec(async function(workspace, tag, registry) {
|
|
105
|
+
await this.cloudScript.update(workspace, tag, { registryUrl: resolveRegistryUrl(registry) });
|
|
106
|
+
}),
|
|
107
|
+
pushUpdate: target({
|
|
108
|
+
desc: "Pack an over-the-air update of a phone's web bundle and upload it to the akasys project, which signs and publishes it"
|
|
109
|
+
}).with(App).option("platform", String, { enum: ["ios", "android"], desc: "the app it updates" }).option("target", String, targetOption).option("env", String, envOption).option("channel", String, { desc: "the channel (default: the target's updates.channel)", nullable: true }).option("notes", String, { desc: "release notes shown in the project", nullable: true }).option("subspace", String, { desc: "the subspace whose project gets it", nullable: true }).option("host", String, hostOption).option("write", Boolean, { desc: "write code generation", default: true }).option("allowLocalRelease", Boolean, { flag: "l", desc: "allow packing with --env local", default: false }).with(Workspace).exec(async function(app, platform, target, env, channel, notes, subspace, host, write, allowLocalRelease, workspace) {
|
|
110
|
+
await this.cloudScript.pushUpdate(workspace, app, {
|
|
111
|
+
platform,
|
|
112
|
+
target,
|
|
113
|
+
env,
|
|
114
|
+
channel,
|
|
115
|
+
notes,
|
|
116
|
+
subspace,
|
|
117
|
+
host,
|
|
118
|
+
write,
|
|
119
|
+
allowLocalRelease
|
|
120
|
+
});
|
|
121
|
+
}),
|
|
122
|
+
registerBinary: target({
|
|
123
|
+
desc: "Register the store build of a phone app with the akasys project, with a pack of the same commit as its embedded bundle"
|
|
124
|
+
}).with(App).option("platform", String, { enum: ["ios", "android"], desc: "the store build to register" }).option("distribution", String, {
|
|
125
|
+
enum: ["appStore", "testFlight", "playStore", "playTesting", "direct"],
|
|
126
|
+
desc: "where the binary ships"
|
|
127
|
+
}).option("target", String, targetOption).option("env", String, envOption).option("subspace", String, { desc: "the subspace whose project gets it", nullable: true }).option("host", String, hostOption).option("write", Boolean, { desc: "write code generation", default: true }).option("allowLocalRelease", Boolean, { flag: "l", desc: "allow packing with --env local", default: false }).with(Workspace).exec(async function(app, platform, distribution, target, env, subspace, host, write, allowLocalRelease, workspace) {
|
|
128
|
+
await this.cloudScript.registerBinary(workspace, app, {
|
|
129
|
+
platform,
|
|
130
|
+
distribution,
|
|
131
|
+
target,
|
|
132
|
+
env,
|
|
133
|
+
subspace,
|
|
134
|
+
host,
|
|
135
|
+
write,
|
|
136
|
+
allowLocalRelease
|
|
137
|
+
});
|
|
138
|
+
}),
|
|
139
|
+
uploadSecret: target({
|
|
140
|
+
desc: "Upload a named pipeline secret to the akasys project; write-only, it can never be read back"
|
|
141
|
+
}).arg("name", String, { desc: "secret name, e.g. DISCORD_WEBHOOK" }).option("branch", String, {
|
|
142
|
+
desc: "only builds of this branch use it: debug, develop or main (default: every branch)",
|
|
143
|
+
nullable: true
|
|
144
|
+
}).option("file", String, {
|
|
145
|
+
desc: "read the value from this file instead of stdin or a hidden prompt",
|
|
146
|
+
nullable: true
|
|
147
|
+
}).option("subspace", String, { desc: "the subspace whose project gets it", nullable: true }).option("host", String, {
|
|
148
|
+
desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
149
|
+
nullable: true
|
|
150
|
+
}).with(Workspace).exec(async function(name, branch, file, subspace, host, workspace) {
|
|
151
|
+
await this.cloudScript.uploadSecret(workspace, { name, branch, file, subspace, host });
|
|
152
|
+
}),
|
|
153
|
+
listSecrets: target({ desc: "List the pipeline secrets of the akasys project: names and scopes, never values" }).option("branch", String, {
|
|
154
|
+
desc: "only the secrets builds of this branch use: debug, develop or main",
|
|
155
|
+
nullable: true
|
|
156
|
+
}).option("subspace", String, { desc: "the subspace whose project to list", nullable: true }).option("host", String, {
|
|
157
|
+
desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
158
|
+
nullable: true
|
|
159
|
+
}).with(Workspace).exec(async function(branch, subspace, host, workspace) {
|
|
160
|
+
await this.cloudScript.listSecrets(workspace, { branch, subspace, host });
|
|
161
|
+
}),
|
|
162
|
+
removeSecret: target({ desc: "Remove a named pipeline secret from the akasys project" }).arg("name", String, { desc: "secret name" }).option("branch", String, {
|
|
163
|
+
desc: "the branch override to remove: debug, develop or main (default: the every-branch value)",
|
|
164
|
+
nullable: true
|
|
165
|
+
}).option("subspace", String, { desc: "the subspace whose project holds it", nullable: true }).option("host", String, {
|
|
166
|
+
desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
167
|
+
nullable: true
|
|
168
|
+
}).with(Workspace).exec(async function(name, branch, subspace, host, workspace) {
|
|
169
|
+
await this.cloudScript.removeSecret(workspace, { name, branch, subspace, host });
|
|
170
|
+
}),
|
|
171
|
+
downloadEnv: target({ desc: "Download environment variables from cloud or SCP server" }).option("host", String, {
|
|
172
|
+
desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
173
|
+
nullable: true
|
|
174
|
+
}).with(Workspace).exec(async function(host, workspace) {
|
|
175
|
+
await this.cloudScript.downloadEnv(workspace, undefined, { host });
|
|
176
|
+
}),
|
|
177
|
+
uploadEnv: target({ desc: "Upload environment variables to cloud or SCP server" }).option("host", String, {
|
|
178
|
+
desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
|
|
179
|
+
nullable: true
|
|
180
|
+
}).with(Workspace).exec(async function(host, workspace) {
|
|
181
|
+
await this.cloudScript.uploadEnv(workspace, { host });
|
|
182
|
+
})
|
|
183
|
+
})) {
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
CloudCommand
|
|
187
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
CloudScript2
|
|
4
|
+
} from "./index-tn4rpqp1.js";
|
|
5
|
+
import"./index-y8q31011.js";
|
|
6
|
+
import"./index-p9ystjrp.js";
|
|
7
|
+
import"./index-qt22xstj.js";
|
|
8
|
+
import"./index-c1x0be5j.js";
|
|
9
|
+
import"./index-gnyf1fqy.js";
|
|
10
|
+
import"./index-kjvrq3nc.js";
|
|
11
|
+
import"./index-r2tpeb3n.js";
|
|
12
|
+
import"./index-6jjtnhkw.js";
|
|
13
|
+
import"./index-7md2g9vw.js";
|
|
14
|
+
import"./index-k9rrten6.js";
|
|
15
|
+
import"./index-55mynnqs.js";
|
|
16
|
+
import"./index-2ahj896a.js";
|
|
17
|
+
import"./index-52w0fvh8.js";
|
|
18
|
+
export {
|
|
19
|
+
CloudScript2 as CloudScript
|
|
20
|
+
};
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
runner,
|
|
5
5
|
script,
|
|
6
6
|
command
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-6jjtnhkw.js";
|
|
8
8
|
import"./index-52w0fvh8.js";
|
|
9
|
-
import"./index-
|
|
9
|
+
import"./index-55mynnqs.js";
|
|
10
10
|
import {
|
|
11
11
|
akanCodeDefaultModel2,
|
|
12
12
|
CodeAgentStreamPrinter2
|
|
@@ -19,7 +19,7 @@ class CodeRunner extends runner("code") {
|
|
|
19
19
|
async serve(options, listen) {
|
|
20
20
|
await import("./consoleToStderr-h5bryj94.js");
|
|
21
21
|
const [{ CodeAgent }, { CodeAgentRpcHost }, { CodeAgentRpcListener }] = await Promise.all([
|
|
22
|
-
import("./CodeAgent-
|
|
22
|
+
import("./CodeAgent-q7a4srt6.js"),
|
|
23
23
|
import("./CodeAgentRpcHost-p7h6n2hp.js"),
|
|
24
24
|
import("./CodeAgentRpcListener-7p7z5dd7.js")
|
|
25
25
|
]);
|
|
@@ -34,8 +34,8 @@ class CodeRunner extends runner("code") {
|
|
|
34
34
|
}
|
|
35
35
|
async tui(options, seed) {
|
|
36
36
|
const [{ CodeAgent }, { CodeTui }] = await Promise.all([
|
|
37
|
-
import("./CodeAgent-
|
|
38
|
-
import("./CodeTui-
|
|
37
|
+
import("./CodeAgent-q7a4srt6.js"),
|
|
38
|
+
import("./CodeTui-ak23dxc9.js")
|
|
39
39
|
]);
|
|
40
40
|
const agentOptions = await CodeRunner.#agentOptions(options, false);
|
|
41
41
|
let resume = options.resume;
|
|
@@ -60,7 +60,7 @@ class CodeRunner extends runner("code") {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
async run(prompt, options) {
|
|
63
|
-
const { CodeAgent } = await import("./CodeAgent-
|
|
63
|
+
const { CodeAgent } = await import("./CodeAgent-q7a4srt6.js");
|
|
64
64
|
const agent = await CodeAgent.create(await CodeRunner.#agentOptions(options, false));
|
|
65
65
|
const printer = new CodeAgentStreamPrinter2({ json: options.json, thinking: options.thinking });
|
|
66
66
|
agent.on((event) => printer.print(event));
|
package/commandManifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"byCommand":{"create-workspace":"workspace","generate-agent-rules":"workspace","lint":"workspace","lint-all":"workspace","sync-all":"workspace","init":"workspace","agent":"agent","code":"code","create-application":"application","remove-application":"application","plan-slice":"application","sync":"application","script":"application","console":"application","logs":"application","build":"application","b":"application","typecheck":"application","t":"application","test":"application","build-ios":"application","bi":"application","build-android":"application","ba":"application","build-desktop":"application","bd":"application","start":"application","s":"application","start-ios":"application","si":"application","start-android":"application","sa":"application","start-desktop":"application","sd":"application","release-ios":"application","release-android":"application","pack-update":"application","update-keygen":"application","publish-update":"application","dbup":"application","db-export":"application","db-import":"application","dbdown":"application","create-library":"library","remove-library":"library","sync-library":"library","library-status":"library","install-library":"library","start-registry":"localRegistry","reset-registry":"localRegistry","smoke-registry":"localRegistry","version":"package","create-package":"package","remove-package":"package","sync-package":"package","build-package":"package","verify-dist-package":"package","verify-akan-publish-packages":"package","create-module":"module","remove-module":"module","create-service":"module","create-view":"module","create-unit":"module","create-template":"module","create-crud-page":"page","context":"context","doctor":"context","mcp-install":"context","mcp":"context","mcp-call":"context","login":"cloud","logout":"cloud","deploy-akan":"cloud","test-platforms":"cloud","update":"cloud","download-env":"cloud","upload-env":"cloud","subspace":"subspace","guideline":"guideline","create-scalar":"scalar","remove-scalar":"scalar","create-ui":"primitive","add-field":"primitive","add-enum-field":"primitive","quality":"quality","repair":"repair","workflow":"workflow","tunnel":"tunnel"}}
|
|
1
|
+
{"byCommand":{"create-workspace":"workspace","generate-agent-rules":"workspace","lint":"workspace","lint-all":"workspace","machine-keygen":"workspace","sync-all":"workspace","init":"workspace","agent":"agent","code":"code","create-application":"application","remove-application":"application","plan-slice":"application","sync":"application","script":"application","console":"application","logs":"application","build":"application","b":"application","typecheck":"application","t":"application","test":"application","build-ios":"application","bi":"application","build-android":"application","ba":"application","build-desktop":"application","bd":"application","start":"application","s":"application","start-ios":"application","si":"application","start-android":"application","sa":"application","start-desktop":"application","sd":"application","release-ios":"application","release-android":"application","pack-update":"application","update-keygen":"application","publish-update":"application","dbup":"application","db-export":"application","db-import":"application","dbdown":"application","create-library":"library","remove-library":"library","sync-library":"library","library-status":"library","install-library":"library","start-registry":"localRegistry","reset-registry":"localRegistry","smoke-registry":"localRegistry","version":"package","create-package":"package","remove-package":"package","sync-package":"package","build-package":"package","verify-dist-package":"package","verify-akan-publish-packages":"package","create-module":"module","remove-module":"module","create-service":"module","create-view":"module","create-unit":"module","create-template":"module","create-crud-page":"page","context":"context","doctor":"context","mcp-install":"context","mcp":"context","mcp-call":"context","login":"cloud","logout":"cloud","deploy-akan":"cloud","test-platforms":"cloud","update":"cloud","push-update":"cloud","register-binary":"cloud","upload-secret":"cloud","list-secrets":"cloud","remove-secret":"cloud","download-env":"cloud","upload-env":"cloud","subspace":"subspace","guideline":"guideline","create-scalar":"scalar","remove-scalar":"scalar","create-ui":"primitive","add-field":"primitive","add-enum-field":"primitive","quality":"quality","repair":"repair","workflow":"workflow","pipeline":"pipeline","tunnel":"tunnel"}}
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
import {
|
|
3
3
|
Workspace,
|
|
4
4
|
command
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-6jjtnhkw.js";
|
|
6
6
|
import"./index-52w0fvh8.js";
|
|
7
|
-
import"./index-
|
|
7
|
+
import"./index-55mynnqs.js";
|
|
8
8
|
import"./index-hj6w4ka1.js";
|
|
9
|
-
import"./index-
|
|
10
|
-
import"./index-
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
9
|
+
import"./index-05g3jayg.js";
|
|
10
|
+
import"./index-2mx692qq.js";
|
|
11
|
+
import"./index-0bm04n5g.js";
|
|
12
|
+
import"./index-kjvrq3nc.js";
|
|
13
13
|
import"./index-zzvvc845.js";
|
|
14
|
-
import"./index-
|
|
15
|
-
import"./index-
|
|
16
|
-
import"./index-
|
|
17
|
-
import"./index-
|
|
18
|
-
import"./index-
|
|
19
|
-
import"./index-
|
|
14
|
+
import"./index-fm46xrf1.js";
|
|
15
|
+
import"./index-2etcv0sc.js";
|
|
16
|
+
import"./index-e0cvze1s.js";
|
|
17
|
+
import"./index-ctanya41.js";
|
|
18
|
+
import"./index-c76k723e.js";
|
|
19
|
+
import"./index-rwq3ekqc.js";
|
|
20
20
|
import {
|
|
21
21
|
ContextScript
|
|
22
|
-
} from "./index-
|
|
22
|
+
} from "./index-jyv387qw.js";
|
|
23
23
|
|
|
24
24
|
// pkgs/@akanjs/cli/context/context.command.ts
|
|
25
25
|
class ContextCommand extends command("context", [ContextScript], ({ public: target }) => ({
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ContextRunner2
|
|
4
|
+
} from "./index-0bm04n5g.js";
|
|
5
|
+
import"./index-rwq3ekqc.js";
|
|
6
|
+
import"./index-c76k723e.js";
|
|
7
|
+
import"./index-ctanya41.js";
|
|
8
|
+
import"./index-fm46xrf1.js";
|
|
9
|
+
import"./index-e0cvze1s.js";
|
|
10
|
+
import"./index-2etcv0sc.js";
|
|
11
|
+
import"./index-kjvrq3nc.js";
|
|
12
|
+
import"./index-2mx692qq.js";
|
|
13
|
+
import"./index-05g3jayg.js";
|
|
14
|
+
import"./index-zzvvc845.js";
|
|
15
|
+
import"./index-6jjtnhkw.js";
|
|
16
|
+
import"./index-55mynnqs.js";
|
|
17
|
+
import"./index-hj6w4ka1.js";
|
|
18
|
+
import"./index-52w0fvh8.js";
|
|
19
|
+
export {
|
|
20
|
+
ContextRunner2 as ContextRunner
|
|
21
|
+
};
|
|
@@ -47,7 +47,9 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
47
47
|
`AgentProvider` or an `Agent.Zone` belongs to whoever provided it.
|
|
48
48
|
- **The panel is customized in three widening steps, and most designs stop at the first.** Props first:
|
|
49
49
|
`open` / `onOpenChange` drive it from the app's own control (`launcher={false}` then draws no launcher of its
|
|
50
|
-
own), `
|
|
50
|
+
own), `launcherPlacement` / `hideLauncherBelow` place the launcher — which sits above the page's bottom chrome
|
|
51
|
+
and under every menu and modal — `launcherClassName` / `panelClassName` reach one surface each where
|
|
52
|
+
`className` reaches both, `intro`
|
|
51
53
|
replaces the empty-state line — where starter questions go — and `header` adds controls beside the built-in
|
|
52
54
|
clear and close. `chrome={false}` drops the header bar whole, for an `inline` chat inside a panel the app
|
|
53
55
|
already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
|
|
@@ -306,7 +308,9 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
306
308
|
an inline arrow can be clicked by a person and by nobody else. `.desc()` is required and comes first, `.arg()`
|
|
307
309
|
is an argument the caller must pass and `.opt()` one it may leave out (the `exec` parameter is then `| null`),
|
|
308
310
|
and `.exec()` is the only hook, so the chain completes in one unconditional statement and the callable carries
|
|
309
|
-
`data-akan-action` like a store setter does. A `remove*` name defaults to a confirm gate.
|
|
311
|
+
`data-akan-action` like a store setter does. A `remove*` name defaults to a confirm gate. `st.tool("x", { label:
|
|
312
|
+
l("ticket.archive") })` gives the action a name a person reads — `Agent.useScreenActions()` hands it to a command
|
|
313
|
+
palette, and the model never sees it. Reach a store action
|
|
310
314
|
from the body — `.exec((id) => st.do.removeX(id))` — which is how an agent gets CRUD; `st.do` on its own
|
|
311
315
|
reaches nobody.
|
|
312
316
|
- **`.card()` is the other way the chain ends, for an answer that is the user's to give.** Where `.exec()` runs a
|
|
@@ -357,6 +361,10 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
357
361
|
(a different `modal`, a different redirect) cannot honestly share a description, and that pair wants a
|
|
358
362
|
`namespace` or nothing at all. The ids come from the `<slice>.items` resource `Load.Units` and
|
|
359
363
|
`Data.ListContainer` already expose.
|
|
364
|
+
- **A command palette lists the same declarations.** `Agent.useScreenActions()` returns the screen's published
|
|
365
|
+
tools that take no required argument, each with a `run` that calls the control's own handler (guard included,
|
|
366
|
+
not logged as an agent call) and `needsConfirm` for the ones a person should confirm — so ⌘K offers exactly
|
|
367
|
+
what the agent can do here, with nothing declared twice.
|
|
360
368
|
|
|
361
369
|
## What `akanjs/ui` Publishes For You
|
|
362
370
|
- **`akanjs/ui` publishes its own controls, so an app writes nothing for them.** `Data.ListContainer` (and every
|
|
@@ -67,7 +67,9 @@ back.
|
|
|
67
67
|
- **Never import a third-party package** from `page/**`, from any barrel, or from any
|
|
68
68
|
`*.{constant,dictionary,document,service,signal,store}.ts` / `*.{Template,Unit,Util,View,Zone}.tsx`
|
|
69
69
|
(`no-import-external-library.grit`). Re-export the symbol through a lib first. One-line re-export shims in a lib's
|
|
70
|
-
`common/`, `webkit/`, or `ui/` exist for exactly this reason — they are load-bearing, not cruft.
|
|
70
|
+
`common/`, `webkit/`, or `ui/` exist for exactly this reason — they are load-bearing, not cruft. A module's
|
|
71
|
+
`*.signal.test.ts` / `*.service.test.ts` is held to the same rule but may import the runtime's `node:*` and
|
|
72
|
+
`bun:*` modules (a temp dir, a socket, sqlite), which module code leaves to `srvkit/`.
|
|
71
73
|
- **`#private` is banned in exactly four file suffixes:** `*.constant.ts`, `*.document.ts`, `*.service.ts`, and
|
|
72
74
|
`*.store.ts` (`no-js-private-class-method.grit`). The rule is scoped by file path, not by class shape, so
|
|
73
75
|
`#private` remains the house style everywhere under `srvkit/`, including `adapt()` adapter classes.
|
|
@@ -202,6 +204,13 @@ back.
|
|
|
202
204
|
- Prefer focused behavior tests for public contracts and edge cases over implementation-detail assertions.
|
|
203
205
|
- Run package suites with `bun run akan test <pkg>` from the repo root, or `cd <pkg> && bun test --isolate`. Plain `bun test` without `--isolate` shares one global object across test files and fails dozens of tests from cross-file state pollution (`bunfig.toml` `[test] isolate` is not honored as of Bun 1.3), and running `bun test` from the repo root breaks subprocess stdio pipes.
|
|
204
206
|
- Split signal tests in two. `<model>.signal.spec.ts` holds reusable fixtures built on `sampleOf(cnst.XInput)` with explicit `Promise<cnst.X>` return types and **no assertions**. `<model>.signal.test.ts` holds the assertions: `describe("<Model> Signal")`, `let` fixtures at describe scope, one `beforeAll`, story-ordered `it`s, and negatives via `await expect(p).rejects.toThrow()`.
|
|
207
|
+
- **One suite per module file, named after it.** A module test is `<model>.<role>.test.ts` beside the file it covers
|
|
208
|
+
(`chatMessage.signal.test.ts`, `chatMessage.service.test.ts`, `ChatMessage.Zone.test.tsx`). Never split one role
|
|
209
|
+
across topic files such as `chatMessage.trim.signal.test.ts` / `chatMessage.security.signal.test.ts` — add a
|
|
210
|
+
`describe` block to the existing suite. A suite that drives several modules (guards across models, an app's MCP
|
|
211
|
+
exposure list) belongs to none of them and goes in `srvkit/`, like `libs/shared/srvkit/guards.signal.test.ts`.
|
|
212
|
+
Never put a test at the `lib/` root. `akan sync` refuses all three shapes. Nothing imports a test, so a build never
|
|
213
|
+
typechecks one; `akan typecheck <app-or-lib>` does, `bun:test` types included.
|
|
205
214
|
- `lib/user/user.signal.spec.ts` is the one place agent types are re-exported and re-typed; import `UserAgent` / `AdminAgent` from there rather than from the owning lib directly.
|
|
206
215
|
- A placeholder `it` with a descriptive title is an acceptable floor. Write a real suite when the behaviour is security-relevant.
|
|
207
216
|
|
|
@@ -310,11 +319,11 @@ are, because those are the ones the server could have done.
|
|
|
310
319
|
consume them in a server component, never as a `Zone` prop.
|
|
311
320
|
6. **Use named `ReactNode` slots, not just `children`** — a client shell that takes one slot per region lets a
|
|
312
321
|
server component supply each of them, instead of absorbing the whole subtree into the bundle. `Layout.Navbar`
|
|
313
|
-
|
|
314
|
-
copy the shape rather than the component.
|
|
322
|
+
is the shape: `title` / `left` / `right` beside `back` and `children`, each filled by the server.
|
|
315
323
|
7. **Let the server do the derived work.** Display and predicate logic belongs on `Light<Model>`; enum→class
|
|
316
324
|
lookups belong in a module-scope `as const` map.
|
|
317
|
-
8. **Gate auth on the server** — `getSelf({ unauthorize: "/signin" })` in `_layout.tsx`, before any
|
|
325
|
+
8. **Gate auth on the server** — `getSelf({ unauthorize: "/signin" })` in `_layout.tsx`, before any of the page's
|
|
326
|
+
markup renders. After its fetch the redirect is an in-page one (HTTP 200); `.config({ ssr: "block" })` makes it a 3xx.
|
|
318
327
|
9. **Prefer CSS over client state for pure visibility.** A `data-*` attribute plus `group-data-[…]` variants, or
|
|
319
328
|
`<details>`/`<summary>`, keeps both branches server-rendered.
|
|
320
329
|
10. **Keep the heavy island out of the first load** — behind the `ui/<Folder>/index_.tsx` + `lazy()` pair.
|
|
@@ -379,8 +388,8 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
|
|
|
379
388
|
from `akan start`; `build-ios` / `build-android` / `build-desktop` write
|
|
380
389
|
`dist/native/<app>/<target>/build/<platform>`, a desktop app only for the OS that builds it — `--arch` picks
|
|
381
390
|
a Windows or Linux app's CPU, and a macOS app is Apple silicon only. `--installer true` adds a Windows setup, a
|
|
382
|
-
macOS dmg or a Linux AppImage; `
|
|
383
|
-
`
|
|
391
|
+
macOS dmg or a Linux AppImage; `--sign` signs with the identity `AKAN_NATIVE_MACOS_*` / `AKAN_NATIVE_WINDOWS_*`
|
|
392
|
+
names and `--notarize` uploads the signed macOS app to Apple — the env alone does neither. With `updates: { url, publicKey }` an installed app takes the signed
|
|
384
393
|
releases `akan publish-update` makes; `akan update-keygen` makes the key and prints its `publicKey`.
|
|
385
394
|
- **A desktop app that carries its server (`desktop: { server: true }` in `native`, or in one target) gets nothing
|
|
386
395
|
from `docker`** — and `server: { omit: [...] }` leaves out packages only the image needs. That server is API-only, runs in database mode `single`, and listens on a loopback port any
|
|
@@ -393,7 +402,9 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
|
|
|
393
402
|
`NativeFile.resolve(grant, "read")` in `akanjs/server` asks the shell for it. What it carries is readable on the
|
|
394
403
|
user's computer — `private/` (each lib's too, under `private/libs/<lib>`), the one `env.server.<env>.ts` of its
|
|
395
404
|
`--env` and its libs' server env defaults (`env.server.testing.ts`) — and it has no `public/`: read runtime files
|
|
396
|
-
from `AKAN_APP_DIR`, never `process.cwd()`.
|
|
405
|
+
from `AKAN_APP_DIR`, never `process.cwd()`. **`server: { profile: "edge" }` bundles `env.server.<env>.edge.ts` in
|
|
406
|
+
that file's place**, and the build refuses one holding a secret-named value; `server: { modules: ["@edge"] }`
|
|
407
|
+
mounts only the modules whose `serve()` lists `operationMode: ["edge"]`, and what they reach.
|
|
397
408
|
- **`assets: { pruneFonts, keepFonts }`** trims from the `dist` copy of `public/` the fonts nothing reads; source
|
|
398
409
|
trees are never touched. A font with `optimize` on is a build input, not a runtime asset. `keepFonts` belongs to
|
|
399
410
|
the `akan.config.ts` that owns the font, written against that scope's own `public/`.
|
|
@@ -481,7 +492,9 @@ count was shortened to `count(query)`, with `countDocuments` kept as `@deprecate
|
|
|
481
492
|
`removeById(id)` are those same query-level writes narrowed to one id, **not** the document path: they fire no hooks,
|
|
482
493
|
so a model whose removal cascades or carries a `_postRemove` still goes through `remove<Model>(id)`. The
|
|
483
494
|
write-through pair is `pickAndWrite(id, data)` / `pickOneAndWrite(query, data)` — pick the document, write into it
|
|
484
|
-
and save it, so the save hooks do run; reach for those when the write has to be seen by them.
|
|
495
|
+
and save it, so the save hooks do run; reach for those when the write has to be seen by them. `upsertById(data, {
|
|
496
|
+
preserveTimestamps })` writes a record whole under its own id, new or in place, with no hook: the write a sync or a
|
|
497
|
+
backfill repeats safely, keeping the record's `createdAt`/`updatedAt` when asked.
|
|
485
498
|
|
|
486
499
|
**`<model>.service.ts`** — keep methods to a few lines: load → chain → `return await ….save()`. Write `return await`
|
|
487
500
|
explicitly in tail position; do not "optimize" it away. Side effects belong in `override async _preUpdate` /
|
|
@@ -517,7 +530,9 @@ workflow changes.
|
|
|
517
530
|
|
|
518
531
|
- Keep domain business operations in `.service.ts` classes built with `serve(...)`.
|
|
519
532
|
- Keep execution contracts and triggers in `.signal.ts` classes built with `internal(...)`, `slice(...)`, and `endpoint(...)`.
|
|
520
|
-
- Use `Internal` for internal triggers such as init, interval, cron, or queue jobs.
|
|
533
|
+
- Use `Internal` for internal triggers such as init, interval, cron, or queue jobs. A `process` job's
|
|
534
|
+
`backoff: { type: "exponential", delay, maxDelay }` caps the wait between attempts, and the internal's
|
|
535
|
+
`onFailed(job, error)` runs once a job has failed its last attempt — the dead letter, on either queue.
|
|
521
536
|
- Use `Slice` for typed data views that feed client stores and zones; keep each slice focused on one purpose.
|
|
522
537
|
- Use `Endpoint` for query and mutation contracts exposed to callers.
|
|
523
538
|
- Connect external APIs or infrastructure through adapters, usually under `srvkit/`, and inject them into services instead of importing vendor clients directly into domain logic.
|
|
@@ -541,6 +556,11 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
|
|
|
541
556
|
inside a guard or middleware). Behind a federation gateway, `ws.remoteAddress` and the child's own peer are the
|
|
542
557
|
*gateway* (`127.0.0.1`) for every caller and for the whole life of every socket. `Ip` reads what a proxy
|
|
543
558
|
recorded, is unwrapped from its `::ffff:` form, and is `null` rather than a placeholder when genuinely unknown.
|
|
559
|
+
- **A device or another server proves itself with a machine token, never a staff session.**
|
|
560
|
+
`MachineToken.issue({ kind, id, version })` (`akanjs/server`) signs one with `AKAN_MACHINE_PRIVATE_KEY` (made by
|
|
561
|
+
`akan machine-keygen <file>`), with no expiry; a server holding that key or `AKAN_MACHINE_PUBLIC_KEY` sets
|
|
562
|
+
`account.machine` from a verified one on HTTP and websocket alike, and only from one. Read it with `.with(Machine)`
|
|
563
|
+
or in a guard, and revoke by bumping the version the guard compares to the record's own.
|
|
544
564
|
- **Every socket carries a `socketId`, and only the framework mints one** — read it off the `Ws` internal arg
|
|
545
565
|
(`.with(Ws)` hands `{ ws, socketId, subscribe, on, off }`). Never mint your own from `ws.data`: the room
|
|
546
566
|
bookkeeping keys on the framework's id and a second one fails to match it silently. It identifies a
|
|
@@ -603,7 +623,7 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
|
|
|
603
623
|
|
|
604
624
|
- `.body(...)` / `.param(...)` args accept `ConstantFieldTypeInput` only: scalars, model refs, or `enumOf(...)`.
|
|
605
625
|
- Numbers must use `Int` or `Float` — `Number` is rejected (`pkgs/akanjs/signal/endpointInfo.ts`).
|
|
606
|
-
- `Upload` is valid only inside a mutation flagged for file upload: `mutation([cnst.File], { fileUpload: true }).body("files", [Upload])`, as the `file` module does. It is not a model field type.
|
|
626
|
+
- `Upload` is valid only inside a mutation flagged for file upload: `mutation([cnst.File], { fileUpload: true }).body("files", [Upload])`, as the `file` module does. It is not a model field type. The other body args of such a mutation arrive typed as in a JSON body. A file's `type` on the server is what its name implies, not its bytes — check the bytes when the type matters.
|
|
607
627
|
- Bytes are `Binary`, never `Any` — see Scalar & Field Type Reference. A `Binary` return travels as base64 in an
|
|
608
628
|
HTTP body, and a return declared `Any` that turns out to be bytes fails instead of reaching the caller as `{}`.
|
|
609
629
|
|
|
@@ -669,7 +689,8 @@ Full contract — filter-arg `ref` pickers, `getQueryMeta` summary counters, `la
|
|
|
669
689
|
"archived" })`. The patch cannot trail the filter args, so it lands on a terminal `.set()`; building the chain
|
|
670
690
|
touches nothing.
|
|
671
691
|
- `removeOne` / `updateOne` hit the **newest** match, always, and report only counts — they are for "there is at
|
|
672
|
-
most one of these", not for claiming the next item off a queue.
|
|
692
|
+
most one of these", not for claiming the next item off a queue. A condition in the query is a compare-and-set in
|
|
693
|
+
every mode: of two concurrent calls that both saw the row match, one applies, and `modifiedCount` says which.
|
|
673
694
|
- **A filter may not be keyed after its own model.** A filter `chat` on model `chat` would silently swap the
|
|
674
695
|
single-document `removeChat`/`updateChat` for a hookless query-level one; it throws at boot instead.
|
|
675
696
|
- **"Has no value" is `q.empty`, never `q.missing`.** `q.missing` is the key being *absent from the stored JSON*,
|
|
@@ -735,7 +756,12 @@ Conventions that hold for both shapes:
|
|
|
735
756
|
- `try/catch` is rare and always converts an exception into a decision, never swallows one. Guards catch → `logger.warn` → `return false`; adapters catch → `logger.error` → `return null`; UI uses `try/finally` to reset a spinner. A bodyless `catch {}` is acceptable only with a one-line reason.
|
|
736
757
|
- Store actions do not `try/catch` — let the framework toast the `Err`. Client-side validation failure is `msg.error("<key>")` plus an early return, never a throw.
|
|
737
758
|
- **A server-side `fetch.clone({ origin })` opens no websocket unless `connect: true`**: it makes one-off calls to
|
|
738
|
-
another server, and a socket it opened would reconnect forever with nobody to close it.
|
|
759
|
+
another server, and a socket it opened would reconnect forever with nobody to close it. It never carries the
|
|
760
|
+
visitor's session either: a call to another origin sends only the `jwt` its caller gave the clone.
|
|
761
|
+
- **Retry a call with `retry`, never with a loop of your own.** `fetch.clone({ origin, retry: {} })` (or a call's
|
|
762
|
+
own `{ retry }`) tries again on an unreachable server, a 502/503/504 and a 429 after its Retry-After, backing off
|
|
763
|
+
with jitter; a mutation retries under one `Idempotency-Key` it mints, or the `idempotencyKey` you name, and the
|
|
764
|
+
server runs a mutation once per key and caller, answering a repeat with the first result. A 4xx is never retried.
|
|
739
765
|
- `String(err)` and `${err}` read `Err: <key> {…data}`, the data as one line of JSON with secret-named keys masked,
|
|
740
766
|
so a ``logger.warn(`…: ${String(error)}`)`` keeps the exit code or status the `Err` carried.
|
|
741
767
|
- **A failure another process reported travels as itself.** A server-to-server `fetch.<endpoint>(..., { origin })` restores the remote `Err` — the same class, key and `data` — so a hop that rethrows it answers its own caller with that error and the dictionary translates it. Never wrap the catch in a `new Error`, and never re-key it as a `new Err` of your own: both discard what the endpoint chose to say, and a bare `Error` is generalized to `Internal Server Error` on the way out.
|
|
@@ -768,9 +794,10 @@ it.
|
|
|
768
794
|
`Person` for an act reserved for a person, `mcp: { cru: false }` for the tools whose input models cost the model
|
|
769
795
|
tokens, `outputSchema` for nothing that reaches the model.
|
|
770
796
|
- Settings live in the app's `lib/option.ts` — `option.setMcp({ enabled, readOnly, path, version, instructions,
|
|
771
|
-
allowedOrigins, pageSize, language, outputSchema, rateLimit, auth })`, or `setMcp((env) => …)` for one derived from the server env, **not
|
|
797
|
+
allowedOrigins, pageSize, language, outputSchema, rateLimit, apps, modern, auth })`, or `setMcp((env) => …)` for one derived from the server env, **not
|
|
772
798
|
`main.ts`**. Each field has an `AKAN_MCP_*` env spelling. `AKAN_MCP_READONLY=true` is the read-only-deployment
|
|
773
|
-
valve, not the exposure switch; `AKAN_MCP=false` takes the whole surface off
|
|
799
|
+
valve, not the exposure switch; `AKAN_MCP=false` takes the whole surface off, and an edge server
|
|
800
|
+
(`AKAN_PUBLIC_OPERATION_MODE=edge`) starts with it off unless `AKAN_MCP=true` or `setMcp(true)`. Every call that executes an
|
|
774
801
|
endpoint is rate-limited per caller (120 a minute, 8 in flight, per process; 429 + `Retry-After`) — `rateLimit`
|
|
775
802
|
/ `AKAN_MCP_RATE_LIMIT` tunes or disables it.
|
|
776
803
|
- **Authentication is the same server.** An app mounting `libs/shared` serves the OAuth 2.1 authorization server
|
|
@@ -811,7 +838,13 @@ it.
|
|
|
811
838
|
from, minus what is already attached; lists — only lists — are cut to `promptBudget`. A guard refusing a query
|
|
812
839
|
inside the body reads as the screen refusing the account, the same way a redirect does. A
|
|
813
840
|
required argument left out answers a pointer to the `<model>List…` tool, never a guess. The description is the
|
|
814
|
-
only instruction, in English — `<Agent.Guide>` never reaches
|
|
841
|
+
only instruction, in English — `<Agent.Guide>` never reaches a prompt, and the in-page chat lists no page prompts.
|
|
842
|
+
- **The same page is a tool too, and an MCP Apps host renders it.** A `.prompt()` page answers `tools/call` with
|
|
843
|
+
that briefing plus its web URL, and a host that declares the `io.modelcontextprotocol/ui` extension (claude.ai,
|
|
844
|
+
ChatGPT, VS Code) shows the screen itself in the chat — the CSR bundle in a sandboxed iframe whose every call the
|
|
845
|
+
host relays under the agent's token. An endpoint kept off the shelf only by `mcp: false` stays callable by that
|
|
846
|
+
view and is listed to no model; a `Person` act is still refused there. `option.setMcp({ apps: false })` keeps
|
|
847
|
+
pages prompts only, and a page named like an endpoint stays one (boot warns).
|
|
815
848
|
|
|
816
849
|
Full contract — configuration, wire behaviour, resource URIs, OAuth metadata, protocol revisions, and
|
|
817
850
|
`McpProgress.report`: `get_guideline` with `mcpRule`, or `akan guideline show mcpRule`.
|
|
@@ -905,7 +938,7 @@ Full contract — the trigger-maintained mirror, tokenizer changes, `AKAN_SEARCH
|
|
|
905
938
|
backstop, including for a `text` field *underneath* one of those. Do not work around either.
|
|
906
939
|
- The role works on a relation (`image: field(File, { text: "thumb" })`) and on an array; an array of objects
|
|
907
940
|
indexes by leaf key. A field inside a `Map` indexes nothing — there is no fixed path to extract it from.
|
|
908
|
-
- Search runs in every database mode — fts5 on SQLite
|
|
941
|
+
- Search runs in every database mode — fts5 on SQLite, a weighted `tsvector` (`pg_trgm` for `trigram`) on
|
|
909
942
|
Postgres. The same text matches the same documents; only the order may differ on Postgres.
|
|
910
943
|
|
|
911
944
|
### Image & File Fields
|
|
@@ -957,9 +990,12 @@ shape, so `cascade` never means "related" — it means one of exactly these:
|
|
|
957
990
|
`_layout.tsx`, `rootLayout()…render(…)` for the app's (or a basePath's) root `_layout.tsx`. Every route setting
|
|
958
991
|
is a stage of the chain — `.param()`, `.search()`, `.config()`, `.head()`, `.loading()`; `.notFound()` /
|
|
959
992
|
`.error()` on a layout; `.fonts()` / `.theme()` / `.manifest()` / `.reconnect()` / `.wsConnect()` /
|
|
960
|
-
`.layoutStyle()` on the root layout; `.prompt()` on a page. A named export beside the chain, or
|
|
961
|
-
`_layout.tsx`, fails the build. `.head()` takes JSX — `<title>`, `<meta>`, `<link>` — or a function
|
|
962
|
-
route's args returning it
|
|
993
|
+
`.layoutStyle()` / `.viewport()` on the root layout; `.prompt()` on a page. A named export beside the chain, or
|
|
994
|
+
`page()` in a `_layout.tsx`, fails the build. `.head()` takes JSX — `<title>`, `<meta>`, `<link>` — or a function
|
|
995
|
+
of the route's args returning it, and one that throws renders no head (warned about) so a layout's sign-in redirect
|
|
996
|
+
still answers; there is no metadata object, and analytics is the app's own script, not a stage.
|
|
997
|
+
Heads merge root first: a deeper `.head()` replaces the `<title>`, a `<meta>` with the same `name` / `property`,
|
|
998
|
+
and a canonical / manifest / icon link, and keeps the rest — so a page states only what it changes.
|
|
963
999
|
- **A CSR bundle (a native app, `?csr=true`) never shows a root layout's own markup.** Pages render into the
|
|
964
1000
|
frame, and what the root layout draws around `{children}` stays in a hidden container, so its wrappers cannot
|
|
965
1001
|
stack a second screen beside the frame. Its components still mount and their effects run (`<Auth.User />` works).
|
|
@@ -971,7 +1007,9 @@ shape, so `cascade` never means "related" — it means one of exactly these:
|
|
|
971
1007
|
Dayjs, an `enumOf` class → its union, `[T]` → array — a path value the type refuses answers not-found, and a
|
|
972
1008
|
search value that fails is dropped. A layout may declare a subset. Names are string literals: `akan sync` reads
|
|
973
1009
|
them off the source and refuses a `[projectId]` folder whose page declares no `.param("projectId")`. `lang` is
|
|
974
|
-
never declared: every route sits under `/:lang`, and the locale segment reaches every stage as `lang`.
|
|
1010
|
+
never declared: every route sits under `/:lang`, and the locale segment reaches every stage as `lang`. On a web
|
|
1011
|
+
page's first render `st.use.searchParams()` is still the server's empty snapshot, synced right after hydration, so
|
|
1012
|
+
an effect that needs the query on its first run takes the page's `.search()` value as a prop.
|
|
975
1013
|
- `_overrides.tsx` is a logic-free UI-override manifest: imports plus a single `export default override({ Slot: AppComponent })` (from `akanjs/ui`), no `"use client"`. It re-skins framework `akanjs/ui` components for its route subtree; nested manifests merge over ancestors slot-by-slot (closest wins). See the UI Customization reference for the slot list.
|
|
976
1014
|
- The legacy shape — `export default async function Page` beside `pageConfig` / `head` / `Loading` exports — still
|
|
977
1015
|
loads and logs one deprecation warning per file at boot. Migrate with the `workspaceRecipes` recipe "Migrating
|
|
@@ -988,10 +1026,10 @@ shape, so `cascade` never means "related" — it means one of exactly these:
|
|
|
988
1026
|
export default page()
|
|
989
1027
|
.param("orgId", ID)
|
|
990
1028
|
.config({ transition: "stack" })
|
|
991
|
-
.render(
|
|
1029
|
+
.render(({ orgId }) => {
|
|
992
1030
|
const { l } = usePage();
|
|
993
1031
|
getSelf({ unauthorize: "/signin" });
|
|
994
|
-
const
|
|
1032
|
+
const { taskInitInOrg } = fetch.initTaskInOrg(orgId);
|
|
995
1033
|
return <Task.Zone.Card init={taskInitInOrg} prefix={`/org/${orgId}`} />;
|
|
996
1034
|
});
|
|
997
1035
|
```
|
|
@@ -999,7 +1037,8 @@ export default page()
|
|
|
999
1037
|
- There is no `loader=` / `render=` page prop and no `PageProps` interface: the chain declares the arguments and
|
|
1000
1038
|
`.render()` receives them flat, typed, with `lang` on every route and `children` added for a layout.
|
|
1001
1039
|
- Body order inside `.render()`: `usePage()`, auth, fetch, return.
|
|
1002
|
-
-
|
|
1040
|
+
- Hand each `fetch.init*` / `view*` / `edit*` field to its section unawaited. Await only what the shell itself
|
|
1041
|
+
reads — a title, a redirect, an id for a link — and run those awaits through one `Promise.all`.
|
|
1003
1042
|
- Gate auth at `_layout.tsx`; repeating `getSelf({ unauthorize: "/signin" })` in the page is fine and common.
|
|
1004
1043
|
- `async` on the render callback only when the body awaits; the callback is not a React component, so it takes no
|
|
1005
1044
|
name and no return type.
|
|
@@ -1020,12 +1059,12 @@ export default page()
|
|
|
1020
1059
|
content outside the markers is yours. `akan lint` fails when the generated section is stale.
|
|
1021
1060
|
- The `plugin/` facet holds Akan plugin declarations; files use the `<name>.plugin.ts` convention (e.g. `pushNotification.plugin.ts`) and are re-exported from the generated `plugin/index.ts` barrel.
|
|
1022
1061
|
- Do not add `apps/*/base` or `libs/*/base`; place shared utilities under that app or lib's own `common/`.
|
|
1023
|
-
- `apps/*/lib` and `libs/*/lib` root files are limited to generated/support files: `cnst.ts`, `db.ts`, `dict.ts`, `option.ts`, `sig.ts`, `srv.ts`, `st.ts`, `useClient.ts`, `useServer.ts
|
|
1062
|
+
- `apps/*/lib` and `libs/*/lib` root files are limited to generated/support files: `cnst.ts`, `db.ts`, `dict.ts`, `option.ts`, `sig.ts`, `srv.ts`, `st.ts`, `useClient.ts`, `useServer.ts`. Nothing hand-written sits beside them, tests included: a test goes in the module folder it covers (`lib/<model>/<model>.signal.test.ts`), or in `srvkit/` when it drives several modules.
|
|
1024
1063
|
- Domain module folders are `lib/<model>` for database modules, `lib/_<service>` for service modules, and `lib/__scalar/<scalar>` for scalar modules.
|
|
1025
1064
|
- Database module UI files are limited to `<Model>.Template.tsx`, `<Model>.Unit.tsx`, `<Model>.Util.tsx`, `<Model>.View.tsx`, and `<Model>.Zone.tsx`.
|
|
1026
1065
|
- Service module UI files are limited to `<Service>.Util.tsx` and `<Service>.Zone.tsx`.
|
|
1027
1066
|
- Scalar module UI files are limited to `<Scalar>.Template.tsx` and `<Scalar>.Unit.tsx`.
|
|
1028
|
-
-
|
|
1067
|
+
- A module test is named after the one module file it covers — `<model>.<role>.test.ts` / `.spec.ts`, `<Model>.<Role>.test.tsx` — so `<model>.<topic>.signal.test.ts` is refused, just like `<model>.<topic>.signal.ts`.
|
|
1029
1068
|
- `ui/index.ts`, `webkit/index.ts`, `srvkit/index.ts`, `common/index.ts`, `plugin/index.ts`, and module `lib/**/index.ts` files are generated by scanSync; do not hand-edit or track them.
|
|
1030
1069
|
- Generated facet indexes export only 1-depth files/folders with `export * from "./name";`. A facet folder with
|
|
1031
1070
|
nothing to export syncs to `export {};` (a missing folder gets no barrel), so a stale barrel heals on the next
|
|
@@ -1131,6 +1170,10 @@ actions and state (see `<model>.store.ts` above).
|
|
|
1131
1170
|
exist yet and the whole `libs/` container is watched, which is the safe direction.
|
|
1132
1171
|
- Test production build generation with `bun run akan build <appName>`.
|
|
1133
1172
|
- To test a built artifact locally, run it from the generated app directory with the required Akan runtime environment variables.
|
|
1173
|
+
- A server a test boots in-process takes `start({ listen: false, schedules: false })` to run no internal (init,
|
|
1174
|
+
interval, cron, queue worker); `listen: false` alone still runs them, as a batch replica does. A server process a
|
|
1175
|
+
test spawns from source resolves the workspace from its cwd and gets the root `.env` from Bun, so spawn it inside
|
|
1176
|
+
the repo with `bun --no-env-file`, or spawn the built `dist/apps/<app>` output, which needs neither.
|
|
1134
1177
|
|
|
1135
1178
|
```bash
|
|
1136
1179
|
cd dist/apps/<appName> && USE_AKANJS_PKGS=true AKAN_PUBLIC_REPO_NAME=<repo> AKAN_PUBLIC_SERVE_DOMAIN="<domain>" \
|