@akanjs/devkit 3.1.0-rc.10 → 3.1.0-rc.12
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/codeAgent/agent/AkanCodeCompaction.ts +49 -0
- package/codeAgent/agent/AkanCodeServices.ts +2 -3
- package/codeAgent/agent/CodeAgent.ts +42 -1
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -0
- package/codeAgent/agent/akanCodeCompaction.test.ts +36 -0
- package/codeAgent/agent/codeAgent.test.ts +61 -3
- package/codeAgent/agent/codeAgentContract.test.ts +1 -0
- package/codeAgent/index.ts +1 -0
- package/codeAgent/skills/akan-code/SKILL.md +4 -0
- package/codeAgent/skills/akan-store/SKILL.md +2 -1
- package/package.json +2 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { CodeAgentModel } from "./akanCodeModel";
|
|
3
|
+
|
|
4
|
+
type CompactedModel = Pick<CodeAgentModel, "provider" | "id" | "contextWindow">;
|
|
5
|
+
|
|
6
|
+
// The engine compacts above `contextWindow - reserveTokens`, so a share of the window becomes a reserve per model.
|
|
7
|
+
export class AkanCodeCompaction {
|
|
8
|
+
// Twice the engine's 16,384: at 16k a reasoning model stops mid-thought at the compaction threshold,
|
|
9
|
+
// and the recovery re-sends a near-full window to retry the turn.
|
|
10
|
+
static readonly reserveFloor = 32_768;
|
|
11
|
+
static readonly steps = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] as const;
|
|
12
|
+
|
|
13
|
+
static isStep(percent: number) {
|
|
14
|
+
return AkanCodeCompaction.steps.some((step) => step === percent);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `100` is the default itself, so it is stored as no choice at all. */
|
|
18
|
+
static normalize(percent: number | undefined) {
|
|
19
|
+
if (percent === undefined) return undefined;
|
|
20
|
+
if (!AkanCodeCompaction.isStep(percent))
|
|
21
|
+
throw new Error(`Autocompact takes a percent in steps of 10: ${AkanCodeCompaction.steps.join(", ")}.`);
|
|
22
|
+
return percent === 100 ? undefined : percent;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static thresholdOf(contextWindow: number, percent: number | undefined) {
|
|
26
|
+
const share = percent ? Math.floor((contextWindow * percent) / 100) : contextWindow;
|
|
27
|
+
return Math.min(share, contextWindow - AkanCodeCompaction.reserveFloor);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static apply(settings: SettingsManager, model: CompactedModel, percent: number | undefined) {
|
|
31
|
+
const threshold = AkanCodeCompaction.thresholdOf(model.contextWindow, percent);
|
|
32
|
+
// Keyed by model, so the next model is never checked against a reserve sized for this one's window.
|
|
33
|
+
settings.applyOverrides({
|
|
34
|
+
compaction: {
|
|
35
|
+
modelOverrides: { [`${model.provider}/${model.id}`]: { reserveTokens: model.contextWindow - threshold } },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Why `percent` cannot work on `model`, or undefined when it can. */
|
|
41
|
+
static problemOf(model: CompactedModel, percent: number, keepRecentTokens: number) {
|
|
42
|
+
const name = `${model.provider}/${model.id}`;
|
|
43
|
+
if (model.contextWindow <= 0) return `${name} declares no context window, so it has no share to compact at.`;
|
|
44
|
+
const threshold = AkanCodeCompaction.thresholdOf(model.contextWindow, percent);
|
|
45
|
+
if (threshold >= keepRecentTokens) return undefined;
|
|
46
|
+
const k = (tokens: number) => `${Math.round(tokens / 1000)}k`;
|
|
47
|
+
return `Compacting at ${percent}% of the ${k(model.contextWindow)} window of ${name} starts at ${k(threshold)}, under the ${k(keepRecentTokens)} of recent turns every compaction keeps, so every turn would compact again.`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { type InlineExtension, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import type { CodeAgentProfile } from "akanjs/common";
|
|
6
6
|
import { AkanEnvKeys } from "../tools/AkanEnvKeys";
|
|
7
|
+
import { AkanCodeCompaction } from "./AkanCodeCompaction";
|
|
7
8
|
import { type AkanContextFile, AkanContextFiles } from "./AkanContextFiles";
|
|
8
9
|
import { akanCodePaths } from "./akanCodePaths";
|
|
9
10
|
import { akanSystemPrompt } from "./akanSystemPrompt";
|
|
@@ -27,9 +28,7 @@ export class AkanCodeServices {
|
|
|
27
28
|
enableInstallTelemetry: false,
|
|
28
29
|
enableAnalytics: false,
|
|
29
30
|
quietStartup: true,
|
|
30
|
-
|
|
31
|
-
// and the recovery re-sends a near-full window to retry the turn.
|
|
32
|
-
compaction: { reserveTokens: 32_768 },
|
|
31
|
+
compaction: { reserveTokens: AkanCodeCompaction.reserveFloor },
|
|
33
32
|
});
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "akanjs/common";
|
|
26
26
|
import type { Workspace } from "../../commandDecorators";
|
|
27
27
|
import { AkanCodePlugins } from "../tools/AkanCodePlugins";
|
|
28
|
+
import { AkanCodeCompaction } from "./AkanCodeCompaction";
|
|
28
29
|
import { AkanCodeServices } from "./AkanCodeServices";
|
|
29
30
|
import {
|
|
30
31
|
akanCodeModel,
|
|
@@ -52,6 +53,8 @@ export interface CodeAgentOptions {
|
|
|
52
53
|
cwd?: string;
|
|
53
54
|
profile: CodeAgentProfile;
|
|
54
55
|
model?: CodeAgentModelRef;
|
|
56
|
+
/** Percent of the context window that starts a compaction, in steps of 10. */
|
|
57
|
+
autoCompactAt?: number;
|
|
55
58
|
/** Apps whose dev server and preview the turn-end feedback loop watches. */
|
|
56
59
|
apps?: string[];
|
|
57
60
|
extensions?: InlineExtension[];
|
|
@@ -85,6 +88,7 @@ export class CodeAgent {
|
|
|
85
88
|
readonly #replay: CodeAgentEvent[] = [];
|
|
86
89
|
#replayFrom = 0;
|
|
87
90
|
#lastContextTokens: number | undefined;
|
|
91
|
+
#autoCompactAt: number | undefined;
|
|
88
92
|
|
|
89
93
|
private constructor(profile: CodeAgentProfile) {
|
|
90
94
|
this.#profile = profile;
|
|
@@ -95,6 +99,7 @@ export class CodeAgent {
|
|
|
95
99
|
const workspaceRoot = options.workspace.workspaceRoot;
|
|
96
100
|
const cwd = options.cwd ?? workspaceRoot;
|
|
97
101
|
const agent = new CodeAgent(options.profile);
|
|
102
|
+
const autoCompactAt = AkanCodeCompaction.normalize(options.autoCompactAt);
|
|
98
103
|
const settingsManager = AkanCodeServices.settings();
|
|
99
104
|
const modelRuntime = await AkanCodeServices.runtime(workspaceRoot, CodeAgentProxy.fromEnv(options.profile));
|
|
100
105
|
const model = await akanCodeModel(modelRuntime, options.model);
|
|
@@ -146,10 +151,12 @@ export class CodeAgent {
|
|
|
146
151
|
agent.#workspaceRoot = workspaceRoot;
|
|
147
152
|
if (options.profile.session.store !== "memory") agent.#sessionDir = akanCodePaths.sessionsDir(workspaceRoot);
|
|
148
153
|
await agent.#attach(session, options.mode ?? "print");
|
|
154
|
+
const autoCompactWarning = agent.#fitAutoCompact(autoCompactAt);
|
|
149
155
|
agent.#suspended = new CodeAgentSuspended(CodeAgentSuspended.fileOf(agent.#sessionDir, agent.sessionId));
|
|
150
156
|
if (agent.#sessionDir && options.profile.session.store === "file")
|
|
151
157
|
agent.#openMail(akanCodePaths.mailDir(workspaceRoot), cwd);
|
|
152
|
-
for (const message of modelWarnings
|
|
158
|
+
for (const message of [...modelWarnings, ...autoCompactWarning])
|
|
159
|
+
agent.#emit({ type: "notice", level: "warning", message });
|
|
153
160
|
return agent;
|
|
154
161
|
}
|
|
155
162
|
|
|
@@ -232,6 +239,7 @@ export class CodeAgent {
|
|
|
232
239
|
contextTokens: model?.contextWindow,
|
|
233
240
|
// Not `off`: that is a setting of a reasoning model, not the absence of reasoning.
|
|
234
241
|
effort: session.supportsThinking() ? session.thinkingLevel : undefined,
|
|
242
|
+
autoCompactAt: this.#autoCompactAt,
|
|
235
243
|
name: session.sessionName,
|
|
236
244
|
interaction: this.#profile.interaction,
|
|
237
245
|
};
|
|
@@ -396,9 +404,42 @@ export class CodeAgent {
|
|
|
396
404
|
const model = await akanCodeModel(session.modelRuntime, ref);
|
|
397
405
|
if (!model) throw new Error(`Unknown model: ${ref.provider}/${ref.id}`);
|
|
398
406
|
await session.setModel(model);
|
|
407
|
+
for (const message of this.#fitAutoCompact(this.#autoCompactAt))
|
|
408
|
+
this.#emit({ type: "notice", level: "warning", message });
|
|
399
409
|
this.#emit({ type: "session", info: this.info });
|
|
400
410
|
}
|
|
401
411
|
|
|
412
|
+
setAutoCompactAt(percent: number | undefined) {
|
|
413
|
+
const wanted = AkanCodeCompaction.normalize(percent);
|
|
414
|
+
const problem = this.#autoCompactProblem(wanted);
|
|
415
|
+
if (problem) throw new Error(problem);
|
|
416
|
+
this.#autoCompactAt = wanted;
|
|
417
|
+
this.#applyAutoCompact();
|
|
418
|
+
this.#emit({ type: "session", info: this.info });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// A share that fit one window can fall under the kept turns of a smaller one, and would compact every turn.
|
|
422
|
+
#fitAutoCompact(percent: number | undefined) {
|
|
423
|
+
const problem = this.#autoCompactProblem(percent);
|
|
424
|
+
this.#autoCompactAt = problem ? undefined : percent;
|
|
425
|
+
this.#applyAutoCompact();
|
|
426
|
+
return problem ? [`${problem} Autocompact is back to the default.`] : [];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#autoCompactProblem(percent: number | undefined) {
|
|
430
|
+
const session = this.#require();
|
|
431
|
+
const model = session.model;
|
|
432
|
+
if (!model || !percent) return undefined;
|
|
433
|
+
const { keepRecentTokens } = session.settingsManager.getCompactionSettings(model);
|
|
434
|
+
return AkanCodeCompaction.problemOf(model, percent, keepRecentTokens);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Never before `create` has built the session: `settingsManager.reload()` drops whatever `applyOverrides` set.
|
|
438
|
+
#applyAutoCompact() {
|
|
439
|
+
const session = this.#require();
|
|
440
|
+
if (session.model) AkanCodeCompaction.apply(session.settingsManager, session.model, this.#autoCompactAt);
|
|
441
|
+
}
|
|
442
|
+
|
|
402
443
|
async waitForIdle() {
|
|
403
444
|
await this.#require().waitForIdle();
|
|
404
445
|
}
|
|
@@ -83,6 +83,9 @@ export class CodeAgentRpcHost {
|
|
|
83
83
|
case "set_model":
|
|
84
84
|
await this.#agent.setModel({ provider: command.provider, id: command.modelId });
|
|
85
85
|
return await this.#agent.state();
|
|
86
|
+
case "set_auto_compact":
|
|
87
|
+
this.#agent.setAutoCompactAt(command.percent);
|
|
88
|
+
return await this.#agent.state();
|
|
86
89
|
case "get_state":
|
|
87
90
|
return await this.#agent.state(command.sinceSeq === undefined ? {} : { sinceSeq: command.sinceSeq });
|
|
88
91
|
case "shutdown":
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { AkanCodeCompaction } from "./AkanCodeCompaction";
|
|
3
|
+
import { AkanCodeServices } from "./AkanCodeServices";
|
|
4
|
+
|
|
5
|
+
const big = { provider: "deepseek", id: "deepseek-flash", contextWindow: 1_000_000 };
|
|
6
|
+
const small = { provider: "openai", id: "small", contextWindow: 128_000 };
|
|
7
|
+
|
|
8
|
+
describe("AkanCodeCompaction", () => {
|
|
9
|
+
test("a percent is a share of the window, never closer to the edge than the reply's floor", () => {
|
|
10
|
+
expect(AkanCodeCompaction.thresholdOf(1_000_000, 60)).toBe(600_000);
|
|
11
|
+
expect(AkanCodeCompaction.thresholdOf(128_000, 90)).toBe(128_000 - AkanCodeCompaction.reserveFloor);
|
|
12
|
+
expect(AkanCodeCompaction.thresholdOf(1_000_000, undefined)).toBe(1_000_000 - AkanCodeCompaction.reserveFloor);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("only steps of 10 are taken, and 100 is the default rather than a choice", () => {
|
|
16
|
+
expect(AkanCodeCompaction.normalize(60)).toBe(60);
|
|
17
|
+
expect(AkanCodeCompaction.normalize(100)).toBeUndefined();
|
|
18
|
+
expect(AkanCodeCompaction.normalize(undefined)).toBeUndefined();
|
|
19
|
+
for (const wrong of [0, 55, 110, Number.NaN]) expect(() => AkanCodeCompaction.normalize(wrong)).toThrow();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("the engine reads the share back as this model's reserve and leaves other models at the floor", () => {
|
|
23
|
+
const settings = AkanCodeServices.settings();
|
|
24
|
+
AkanCodeCompaction.apply(settings, big, 60);
|
|
25
|
+
expect(settings.getCompactionSettings(big).reserveTokens).toBe(400_000);
|
|
26
|
+
expect(settings.getCompactionSettings(small).reserveTokens).toBe(AkanCodeCompaction.reserveFloor);
|
|
27
|
+
AkanCodeCompaction.apply(settings, big, undefined);
|
|
28
|
+
expect(settings.getCompactionSettings(big).reserveTokens).toBe(AkanCodeCompaction.reserveFloor);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("a share under the turns every compaction keeps is refused, since each turn would compact again", () => {
|
|
32
|
+
expect(AkanCodeCompaction.problemOf(big, 10, 20_000)).toBeUndefined();
|
|
33
|
+
expect(AkanCodeCompaction.problemOf(small, 10, 20_000)).toContain("every turn would compact again");
|
|
34
|
+
expect(AkanCodeCompaction.problemOf({ ...small, contextWindow: 0 }, 50, 20_000)).toContain("no context window");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -24,16 +24,31 @@ const chunk = (delta: object, finish: string | null = null) =>
|
|
|
24
24
|
choices: [{ index: 0, delta, finish_reason: finish }],
|
|
25
25
|
})}\n\n`;
|
|
26
26
|
|
|
27
|
+
const usageChunk = (promptTokens: number) =>
|
|
28
|
+
`data: ${JSON.stringify({
|
|
29
|
+
id: "c1",
|
|
30
|
+
object: "chat.completion.chunk",
|
|
31
|
+
created: 0,
|
|
32
|
+
model: akanCodeDefaultModel.id,
|
|
33
|
+
choices: [],
|
|
34
|
+
usage: { prompt_tokens: promptTokens, completion_tokens: 1, total_tokens: promptTokens + 1 },
|
|
35
|
+
})}\n\n`;
|
|
36
|
+
|
|
27
37
|
let llm: ReturnType<typeof Bun.serve>;
|
|
28
38
|
let llmCalls = 0;
|
|
39
|
+
let reportedTokens = 0;
|
|
29
40
|
beforeAll(() => {
|
|
30
41
|
llm = Bun.serve({
|
|
31
42
|
port: 0,
|
|
32
43
|
fetch: () => {
|
|
33
44
|
llmCalls += 1;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
const usage = reportedTokens ? usageChunk(reportedTokens) : "";
|
|
46
|
+
return new Response(
|
|
47
|
+
`${chunk({ role: "assistant", content: "ok" })}${chunk({}, "stop")}${usage}data: [DONE]\n\n`,
|
|
48
|
+
{
|
|
49
|
+
headers: { "content-type": "text/event-stream" },
|
|
50
|
+
},
|
|
51
|
+
);
|
|
37
52
|
},
|
|
38
53
|
});
|
|
39
54
|
});
|
|
@@ -84,6 +99,49 @@ describe("CodeAgent", () => {
|
|
|
84
99
|
}
|
|
85
100
|
});
|
|
86
101
|
|
|
102
|
+
test("autocompact is carried from creation, announced on the session frame, and refuses a step it lacks", async () => {
|
|
103
|
+
const root = await makeTempRoot();
|
|
104
|
+
const agent = await createAgent(root, { autoCompactAt: 60 });
|
|
105
|
+
try {
|
|
106
|
+
const events: CodeAgentEvent[] = [];
|
|
107
|
+
agent.on((event) => events.push(event));
|
|
108
|
+
expect(agent.info.autoCompactAt).toBe(60);
|
|
109
|
+
expect(() => agent.setAutoCompactAt(55)).toThrow("steps of 10");
|
|
110
|
+
expect(agent.info.autoCompactAt).toBe(60);
|
|
111
|
+
agent.setAutoCompactAt(100);
|
|
112
|
+
expect(agent.info.autoCompactAt).toBeUndefined();
|
|
113
|
+
expect(events.filter((event) => event.type === "session")).toHaveLength(1);
|
|
114
|
+
} finally {
|
|
115
|
+
agent.dispose();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a chosen share reaches the engine: a reply reported past it compacts, and the default lets it pass", async () => {
|
|
120
|
+
const compactions = async (options: Partial<CodeAgentOptions>) => {
|
|
121
|
+
const agent = await createAgent(await makeTempRoot(), options);
|
|
122
|
+
const reasons: string[] = [];
|
|
123
|
+
agent.on((event) => {
|
|
124
|
+
if (event.type === "compaction" && event.phase === "start") reasons.push(event.reason);
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
// The 20k most recent tokens are kept, so a summary needs an older turn behind the filler.
|
|
128
|
+
for (const text of ["hello", "filler ".repeat(20_000)]) {
|
|
129
|
+
await agent.prompt(text);
|
|
130
|
+
await agent.waitForIdle();
|
|
131
|
+
}
|
|
132
|
+
reportedTokens = 650_000;
|
|
133
|
+
await agent.prompt("next");
|
|
134
|
+
await agent.waitForIdle();
|
|
135
|
+
} finally {
|
|
136
|
+
reportedTokens = 0;
|
|
137
|
+
agent.dispose();
|
|
138
|
+
}
|
|
139
|
+
return reasons;
|
|
140
|
+
};
|
|
141
|
+
expect(await compactions({})).toEqual([]);
|
|
142
|
+
expect(await compactions({ autoCompactAt: 60 })).toEqual(["threshold"]);
|
|
143
|
+
});
|
|
144
|
+
|
|
87
145
|
test("a sub-agent hands its plugins the token tally of the tree that opened it", async () => {
|
|
88
146
|
const root = await makeTempRoot();
|
|
89
147
|
const build = AkanCodePlugins.build;
|
package/codeAgent/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Importing this barrel loads the engine (~122MiB resident); a host needing cheap pieces imports the owning module.
|
|
2
2
|
// agent/consoleToStderr is deliberately absent: it takes over globalThis.console, so an RPC entry imports it first.
|
|
3
|
+
export * from "./agent/AkanCodeCompaction";
|
|
3
4
|
export * from "./agent/AkanCodeServices";
|
|
4
5
|
export * from "./agent/akanCodeModel";
|
|
5
6
|
export * from "./agent/akanCodePaths";
|
|
@@ -78,6 +78,9 @@ Stored under `.akan/code/sessions` — project history, so a second checkout of
|
|
|
78
78
|
- `/peers` lists other `akan code` sessions running in this workspace; `/msg <peer> <text>` hands one a
|
|
79
79
|
message.
|
|
80
80
|
- `/clear` wipes the screen and keeps the conversation; `/compact [notes]` shortens what the model carries.
|
|
81
|
+
- `/autocompact <percent>` compacts on its own once the window is that full, in steps of 10 (`/autocompact 60`);
|
|
82
|
+
`100` is the default, which waits until 32k remain for the reply. `akan code --compact-at 60` (`-c`) starts
|
|
83
|
+
with it, and a share under the 20k every compaction keeps is refused.
|
|
81
84
|
|
|
82
85
|
## Model, effort, keys
|
|
83
86
|
|
|
@@ -119,6 +122,7 @@ akan code -i "<prompt>" open the session with the prompt already sent
|
|
|
119
122
|
--app <name> narrow the agent to one app
|
|
120
123
|
--profile local|pod|review|web what it may do (default local)
|
|
121
124
|
--model <provider>/<id> --thinking, --json, --rpc, --resume <id>
|
|
125
|
+
--compact-at <percent> compact once the window is this full (10, 20, … 100)
|
|
122
126
|
```
|
|
123
127
|
|
|
124
128
|
`review` reads and never writes, loads no project context files, and keeps no session on disk. `pod` has
|
|
@@ -26,7 +26,8 @@ For a model `comment` (`<Slice>` is a signal slice suffix, e.g. `InPublic`):
|
|
|
26
26
|
|
|
27
27
|
## Generated state — `st.use.*`
|
|
28
28
|
|
|
29
|
-
`comment`, `commentLoading`, `commentForm`, `commentFormLoading`, `
|
|
29
|
+
`comment`, `commentLoading`, `commentForm`, `commentFormLoading`, `commentFormUploading` (uploads in flight, per
|
|
30
|
+
`File` field — non-empty means do not submit), `commentSubmit`, `commentModal`,
|
|
30
31
|
`commentViewAt`, `commentOperation`; per slice `commentList<Slice>`, `commentListLoading<Slice>`,
|
|
31
32
|
`commentInitList<Slice>`, `commentSelection<Slice>`, `commentInsight<Slice>`, `pageOfComment<Slice>`,
|
|
32
33
|
`lastPageOfComment<Slice>`, `hasMoreOfComment<Slice>`, `limitOfComment<Slice>`, `sortOfComment<Slice>`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "3.1.0-rc.
|
|
3
|
+
"version": "3.1.0-rc.12",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@earendil-works/pi-tui": "1.3.0",
|
|
49
49
|
"@inquirer/prompts": "^8.7.3",
|
|
50
50
|
"@tailwindcss/node": "^4.3.3",
|
|
51
|
-
"akanjs": "3.1.0-rc.
|
|
51
|
+
"akanjs": "3.1.0-rc.12",
|
|
52
52
|
"chalk": "^6.0.1",
|
|
53
53
|
"commander": "^15.0.0",
|
|
54
54
|
"dayjs": "^1.11.23",
|