takomi 2.1.4 → 2.1.5
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/.pi/extensions/takomi-runtime/index.ts +0 -42
- package/.pi/extensions/takomi-runtime/shared.ts +12 -3
- package/.pi/extensions/takomi-subagents/dispatch.ts +22 -10
- package/.pi/extensions/takomi-subagents/live-updates.ts +1 -2
- package/.pi/extensions/takomi-subagents/native-render.ts +36 -37
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +239 -228
- package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +18 -0
- package/package.json +2 -2
- package/src/update-check.js +144 -140
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
|
4
3
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
5
4
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
6
5
|
import { Type } from "typebox";
|
|
@@ -47,9 +46,6 @@ import {
|
|
|
47
46
|
type TakomiSubagentRuntimeEvent,
|
|
48
47
|
} from "./subagent-types";
|
|
49
48
|
import {
|
|
50
|
-
visibleWidth,
|
|
51
|
-
truncateToWidth,
|
|
52
|
-
formatFooterNumber,
|
|
53
49
|
buildTaskPrompt,
|
|
54
50
|
resolvePreferredModel,
|
|
55
51
|
} from "./shared";
|
|
@@ -438,46 +434,8 @@ async function applyProfileDefaultsToTasks(ctx: ExtensionContext, tasks: Orchest
|
|
|
438
434
|
return nextTasks;
|
|
439
435
|
}
|
|
440
436
|
|
|
441
|
-
// stripAnsi, visibleWidth, truncateToWidth, formatFooterNumber
|
|
442
|
-
// are imported from "./shared"
|
|
443
|
-
|
|
444
437
|
function installTakomiFooter(ctx: ExtensionContext, stateRef: { current: TakomiState }): void {
|
|
445
438
|
ctx.ui.setFooter((tui, theme, footerData) => new TakomiFooterComponent(tui, theme, footerData, ctx, () => stateRef.current));
|
|
446
|
-
return;
|
|
447
|
-
ctx.ui.setFooter((_tui, theme, footerData) => ({
|
|
448
|
-
invalidate() {},
|
|
449
|
-
render(width: number): string[] {
|
|
450
|
-
const state = stateRef.current;
|
|
451
|
-
let input = 0;
|
|
452
|
-
let output = 0;
|
|
453
|
-
let cost = 0;
|
|
454
|
-
for (const entry of ctx.sessionManager.getBranch()) {
|
|
455
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
456
|
-
const message = entry.message as AssistantMessage;
|
|
457
|
-
input += message.usage.input;
|
|
458
|
-
output += message.usage.output;
|
|
459
|
-
cost += message.usage.cost.total;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const cwd = theme.fg("dim", ctx.cwd);
|
|
464
|
-
const stats = theme.fg("dim", `↑${formatFooterNumber(input)} ↓${formatFooterNumber(output)} $${cost.toFixed(3)}`);
|
|
465
|
-
const leftPad = " ".repeat(Math.max(1, width - visibleWidth(cwd) - visibleWidth(stats)));
|
|
466
|
-
const topLine = truncateToWidth(cwd + leftPad + stats, width);
|
|
467
|
-
|
|
468
|
-
const extensionStatuses = [...footerData.getExtensionStatuses().entries()]
|
|
469
|
-
.filter(([key]) => key !== "takomi-runtime")
|
|
470
|
-
.map(([, value]) => value)
|
|
471
|
-
.filter(Boolean);
|
|
472
|
-
const runtimeStatus = renderRuntimeStatus(theme, state);
|
|
473
|
-
const left = [runtimeStatus, ...extensionStatuses].join(theme.fg("dim", " · "));
|
|
474
|
-
const right = theme.fg("dim", ctx.model?.id || "no-model");
|
|
475
|
-
const rightPad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
|
|
476
|
-
const bottomLine = truncateToWidth(left + rightPad + right, width);
|
|
477
|
-
|
|
478
|
-
return [topLine, bottomLine];
|
|
479
|
-
},
|
|
480
|
-
}));
|
|
481
439
|
}
|
|
482
440
|
|
|
483
441
|
// Mutable state ref so the footer closure always reads the latest state
|
|
@@ -200,6 +200,7 @@ export type JsonRunHooks = {
|
|
|
200
200
|
args?: string;
|
|
201
201
|
isError?: boolean;
|
|
202
202
|
summary?: string;
|
|
203
|
+
invocationId?: string;
|
|
203
204
|
}) => void;
|
|
204
205
|
onStderr?: (chunk: string) => void;
|
|
205
206
|
};
|
|
@@ -274,6 +275,14 @@ function extractAssistantSnapshot(event: Record<string, unknown>, currentText: s
|
|
|
274
275
|
return undefined;
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
function extractToolInvocationId(event: Record<string, unknown>, toolName: string): string {
|
|
279
|
+
for (const key of ["invocationId", "toolCallId", "toolUseId", "callId", "id"]) {
|
|
280
|
+
const value = event[key];
|
|
281
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
282
|
+
}
|
|
283
|
+
return toolName;
|
|
284
|
+
}
|
|
285
|
+
|
|
277
286
|
export function summarizeJsonEvent(event: Record<string, unknown>): string | undefined {
|
|
278
287
|
const type = typeof event.type === "string" ? event.type : "";
|
|
279
288
|
if (type === "tool_execution_start") {
|
|
@@ -356,13 +365,13 @@ export async function runPiAgentJson(cwd: string, args: string[], signal?: Abort
|
|
|
356
365
|
if (event.type === "tool_execution_start") {
|
|
357
366
|
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
358
367
|
const args = typeof event.args === "string" ? event.args : event.args ? JSON.stringify(event.args) : undefined;
|
|
359
|
-
hooks?.onToolEvent?.({ type: "start", toolName, args, summary: messageText });
|
|
368
|
+
hooks?.onToolEvent?.({ type: "start", toolName, args, summary: messageText, invocationId: extractToolInvocationId(event, toolName) });
|
|
360
369
|
} else if (event.type === "tool_execution_update") {
|
|
361
370
|
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
362
|
-
hooks?.onToolEvent?.({ type: "update", toolName, summary: messageText });
|
|
371
|
+
hooks?.onToolEvent?.({ type: "update", toolName, summary: messageText, invocationId: extractToolInvocationId(event, toolName) });
|
|
363
372
|
} else if (event.type === "tool_execution_end") {
|
|
364
373
|
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
365
|
-
hooks?.onToolEvent?.({ type: "end", toolName, isError: event.isError === true, summary: messageText });
|
|
374
|
+
hooks?.onToolEvent?.({ type: "end", toolName, isError: event.isError === true, summary: messageText, invocationId: extractToolInvocationId(event, toolName) });
|
|
366
375
|
}
|
|
367
376
|
|
|
368
377
|
if (event.type === "message_end") {
|
|
@@ -89,12 +89,21 @@ export async function dispatchTakomiSubagent(
|
|
|
89
89
|
let currentToolStartedAt: number | undefined;
|
|
90
90
|
let toolCount = 0;
|
|
91
91
|
let recentTools: Array<{ tool: string; args: string; endMs: number }> = [];
|
|
92
|
+
const activeToolInvocations = new Map<string, { tool: string; args: string; startedAt: number }>();
|
|
92
93
|
let recentOutput: string[] = [];
|
|
93
94
|
|
|
95
|
+
const setCurrentToolFromActive = () => {
|
|
96
|
+
const latest = [...activeToolInvocations.values()].at(-1);
|
|
97
|
+
currentTool = latest?.tool;
|
|
98
|
+
currentToolArgs = latest?.args;
|
|
99
|
+
currentToolStartedAt = latest?.startedAt;
|
|
100
|
+
};
|
|
101
|
+
|
|
94
102
|
const appendRecentOutput = (text: string) => {
|
|
95
103
|
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
96
104
|
if (!lines.length) return;
|
|
97
|
-
recentOutput
|
|
105
|
+
recentOutput.push(...lines);
|
|
106
|
+
if (recentOutput.length > 8) recentOutput.splice(0, recentOutput.length - 8);
|
|
98
107
|
lastActivityAt = Date.now();
|
|
99
108
|
};
|
|
100
109
|
|
|
@@ -210,18 +219,21 @@ export async function dispatchTakomiSubagent(
|
|
|
210
219
|
},
|
|
211
220
|
onToolEvent: (event) => {
|
|
212
221
|
lastActivityAt = Date.now();
|
|
222
|
+
const invocationId = event.invocationId ?? event.toolName;
|
|
213
223
|
if (event.type === "start") {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
activeToolInvocations.set(invocationId, {
|
|
225
|
+
tool: event.toolName,
|
|
226
|
+
args: event.args ?? "",
|
|
227
|
+
startedAt: Date.now(),
|
|
228
|
+
});
|
|
217
229
|
toolCount += 1;
|
|
230
|
+
setCurrentToolFromActive();
|
|
218
231
|
} else if (event.type === "end") {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
232
|
+
const active = activeToolInvocations.get(invocationId);
|
|
233
|
+
recentTools.push({ tool: event.toolName, args: active?.args ?? "", endMs: Date.now() });
|
|
234
|
+
if (recentTools.length > 8) recentTools.splice(0, recentTools.length - 8);
|
|
235
|
+
activeToolInvocations.delete(invocationId);
|
|
236
|
+
setCurrentToolFromActive();
|
|
225
237
|
}
|
|
226
238
|
hooks?.emit?.({
|
|
227
239
|
type: "update",
|
|
@@ -57,7 +57,6 @@ export function createTakomiLiveUpdateBridge(
|
|
|
57
57
|
output: "Queued.",
|
|
58
58
|
stderr: "",
|
|
59
59
|
preflight: "",
|
|
60
|
-
startedAt: Date.now(),
|
|
61
60
|
lastActivityAt: Date.now(),
|
|
62
61
|
recentTools: [],
|
|
63
62
|
recentOutput: ["Queued."],
|
|
@@ -80,7 +79,7 @@ export function createTakomiLiveUpdateBridge(
|
|
|
80
79
|
if (!current) return;
|
|
81
80
|
current.lastActivityAt = Date.now();
|
|
82
81
|
if (event.type === "start") {
|
|
83
|
-
current.startedAt =
|
|
82
|
+
current.startedAt = Date.now();
|
|
84
83
|
current.conversationId = event.state.conversationId ?? current.conversationId;
|
|
85
84
|
current.thinking = event.state.thinking ?? current.thinking;
|
|
86
85
|
current.sessionFile = event.state.sessionFile ?? current.sessionFile;
|
|
@@ -1,37 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
3
|
-
import type {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (params.
|
|
12
|
-
if (params.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
0,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
0,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
1
|
+
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import { renderSubagentResult, syncResultAnimation, type Details } from "./pi-subagents-internal";
|
|
3
|
+
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
5
|
+
import type { TakomiSubagentToolParams } from "./tool-runner";
|
|
6
|
+
|
|
7
|
+
type ToolResult = AgentToolResult<Details>;
|
|
8
|
+
|
|
9
|
+
function taskList(params: TakomiSubagentToolParams): Array<{ agent: string; task: string }> {
|
|
10
|
+
if (params.chain?.length) return params.chain;
|
|
11
|
+
if (params.tasks?.length) return params.tasks;
|
|
12
|
+
if (params.agent || params.task) return [{ agent: params.agent ?? "...", task: params.task ?? "..." }];
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function renderTakomiSubagentCall(params: TakomiSubagentToolParams, theme: Theme) {
|
|
17
|
+
const tasks = taskList(params);
|
|
18
|
+
const mode = params.chain?.length ? "chain" : params.tasks?.length ? "parallel" : "single";
|
|
19
|
+
if (tasks.length === 1) {
|
|
20
|
+
return new Text(
|
|
21
|
+
`${theme.fg("toolTitle", theme.bold("takomi_subagent "))}${theme.fg("accent", tasks[0]?.agent || "?")}`,
|
|
22
|
+
0,
|
|
23
|
+
0,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return new Text(
|
|
27
|
+
`${theme.fg("toolTitle", theme.bold("takomi_subagent "))}${mode} (${tasks.length})`,
|
|
28
|
+
0,
|
|
29
|
+
0,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function renderTakomiSubagentResult(result: ToolResult, options: { expanded?: boolean; isPartial?: boolean }, theme: Theme, context: any) {
|
|
34
|
+
syncResultAnimation(result, context);
|
|
35
|
+
return renderSubagentResult(result, { expanded: options.expanded ?? false }, theme);
|
|
36
|
+
}
|
|
@@ -1,228 +1,239 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
5
|
-
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function
|
|
132
|
-
return {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (mode
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
5
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import {
|
|
7
|
+
createSubagentExecutor,
|
|
8
|
+
discoverPiAgents,
|
|
9
|
+
TEMP_ARTIFACTS_DIR,
|
|
10
|
+
type AgentConfig,
|
|
11
|
+
type AgentScope,
|
|
12
|
+
type Details,
|
|
13
|
+
type ExtensionConfig,
|
|
14
|
+
type SubagentParamsLike,
|
|
15
|
+
type SubagentState,
|
|
16
|
+
} from "./pi-subagents-internal";
|
|
17
|
+
import { resolveAgentName } from "./agent-aliases";
|
|
18
|
+
import type { TakomiSubagentToolParams, TakomiSubagentToolTask } from "./tool-runner";
|
|
19
|
+
|
|
20
|
+
type ToolUpdate = (partial: AgentToolResult<Details>) => void;
|
|
21
|
+
|
|
22
|
+
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
23
|
+
const NEVER_ABORT: AbortSignal = new AbortController().signal;
|
|
24
|
+
|
|
25
|
+
function getSubagentSessionRoot(parentSessionFile: string | null): string {
|
|
26
|
+
if (parentSessionFile) {
|
|
27
|
+
const baseName = path.basename(parentSessionFile, ".jsonl");
|
|
28
|
+
return path.join(path.dirname(parentSessionFile), baseName);
|
|
29
|
+
}
|
|
30
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "takomi-subagent-session-"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function expandTilde(value: string): string {
|
|
34
|
+
return value.startsWith("~/") ? path.join(os.homedir(), value.slice(2)) : value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createState(): SubagentState {
|
|
38
|
+
return {
|
|
39
|
+
baseCwd: process.cwd(),
|
|
40
|
+
currentSessionId: null,
|
|
41
|
+
asyncJobs: new Map(),
|
|
42
|
+
foregroundRuns: new Map(),
|
|
43
|
+
foregroundControls: new Map(),
|
|
44
|
+
lastForegroundControlId: null,
|
|
45
|
+
pendingForegroundControlNotices: new Map(),
|
|
46
|
+
cleanupTimers: new Map(),
|
|
47
|
+
lastUiContext: null,
|
|
48
|
+
poller: null,
|
|
49
|
+
completionSeen: new Map(),
|
|
50
|
+
watcher: null,
|
|
51
|
+
watcherRestartTimer: null,
|
|
52
|
+
resultFileCoalescer: {
|
|
53
|
+
schedule: () => false,
|
|
54
|
+
clear: () => {},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveMode(params: TakomiSubagentToolParams): "single" | "parallel" | "chain" | undefined {
|
|
60
|
+
const hasChain = Boolean(params.chain?.length);
|
|
61
|
+
const hasParallel = Boolean(params.tasks?.length);
|
|
62
|
+
const hasSingle = Boolean(params.agent && params.task);
|
|
63
|
+
if (Number(hasChain) + Number(hasParallel) + Number(hasSingle) !== 1) return undefined;
|
|
64
|
+
return hasChain ? "chain" : hasParallel ? "parallel" : "single";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveTasks(params: TakomiSubagentToolParams): TakomiSubagentToolTask[] {
|
|
68
|
+
if (params.chain?.length) return params.chain;
|
|
69
|
+
if (params.tasks?.length) return params.tasks;
|
|
70
|
+
if (params.agent && params.task) {
|
|
71
|
+
return [{
|
|
72
|
+
agent: params.agent,
|
|
73
|
+
task: params.task,
|
|
74
|
+
workflow: params.workflow,
|
|
75
|
+
skills: params.skills,
|
|
76
|
+
model: params.model,
|
|
77
|
+
fallbackModels: params.fallbackModels,
|
|
78
|
+
thinking: params.thinking,
|
|
79
|
+
conversationId: params.conversationId,
|
|
80
|
+
cwd: params.cwd,
|
|
81
|
+
checklist: params.checklist,
|
|
82
|
+
}];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeThinking(value: unknown): string | undefined {
|
|
88
|
+
return typeof value === "string" && (THINKING_LEVELS as readonly string[]).includes(value) ? value : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildTakomiTaskPrompt(task: TakomiSubagentToolTask): string {
|
|
92
|
+
const checklist = task.checklist?.length
|
|
93
|
+
? [
|
|
94
|
+
"Checklist:",
|
|
95
|
+
...task.checklist.map((item) => typeof item === "string" ? `- [ ] ${item}` : `- [${item.done ? "x" : " "}] ${item.text}`),
|
|
96
|
+
].join("\n")
|
|
97
|
+
: "";
|
|
98
|
+
const takomiContext = [
|
|
99
|
+
task.workflow ? `Takomi workflow: ${task.workflow}` : "",
|
|
100
|
+
task.skills?.length ? `Takomi skills/context overlays: ${task.skills.join(", ")}` : "",
|
|
101
|
+
checklist,
|
|
102
|
+
].filter(Boolean).join("\n\n");
|
|
103
|
+
|
|
104
|
+
return takomiContext ? `${takomiContext}\n\n${task.task}` : task.task;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function modelWithThinking(model: string | undefined, thinking: string | undefined): string | undefined {
|
|
108
|
+
const level = normalizeThinking(thinking);
|
|
109
|
+
if (!model || !level || level === "off") return model;
|
|
110
|
+
if (new RegExp(`:(${THINKING_LEVELS.join("|")})$`, "i").test(model)) return model;
|
|
111
|
+
return `${model}:${level}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function defaultChildExtensions(): string[] {
|
|
115
|
+
// Child runs must not auto-load every user/project extension because this repo
|
|
116
|
+
// currently has both global and project Takomi extensions, which causes tool
|
|
117
|
+
// name conflicts in children. But model providers such as oauth-router are
|
|
118
|
+
// extensions too, so we explicitly allow the provider extension through.
|
|
119
|
+
const roots = [
|
|
120
|
+
process.env.PI_AGENT_ROOT,
|
|
121
|
+
path.join(os.homedir(), ".pi", "agent"),
|
|
122
|
+
path.join(process.cwd(), ".pi"),
|
|
123
|
+
].filter((root): root is string => Boolean(root));
|
|
124
|
+
const candidates = roots.flatMap((root) => [
|
|
125
|
+
path.join(root, "extensions", "oauth-router", "index.ts"),
|
|
126
|
+
path.join(root, "extensions", "oauth-router", "index.js"),
|
|
127
|
+
]);
|
|
128
|
+
return candidates.filter((candidate) => fs.existsSync(candidate));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function withTakomiAgentDefaults(agent: AgentConfig): AgentConfig {
|
|
132
|
+
return {
|
|
133
|
+
...agent,
|
|
134
|
+
systemPromptMode: agent.systemPromptMode ?? "replace",
|
|
135
|
+
inheritProjectContext: agent.inheritProjectContext ?? true,
|
|
136
|
+
inheritSkills: agent.inheritSkills ?? false,
|
|
137
|
+
defaultContext: agent.defaultContext ?? "fresh",
|
|
138
|
+
extensions: [...new Set([...(agent.extensions ?? []), ...defaultChildExtensions()])],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function discoverUnifiedAgents(cwd: string, scope: AgentScope): { agents: AgentConfig[] } {
|
|
143
|
+
return { agents: discoverPiAgents(cwd, scope).agents.map(withTakomiAgentDefaults) };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function agentNameSet(cwd: string): Set<string> {
|
|
147
|
+
return new Set(discoverUnifiedAgents(cwd, "both").agents.map((agent) => agent.name));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mapSingleTask(task: TakomiSubagentToolTask, names: Set<string>) {
|
|
151
|
+
const resolvedAgent = resolveAgentName(task.agent, new Map([...names].map((name) => [name, { name } as any])));
|
|
152
|
+
return {
|
|
153
|
+
agent: resolvedAgent,
|
|
154
|
+
task: buildTakomiTaskPrompt({ ...task, agent: resolvedAgent }),
|
|
155
|
+
cwd: task.cwd,
|
|
156
|
+
model: modelWithThinking(task.model, task.thinking),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function toSubagentParams(params: TakomiSubagentToolParams, rootCwd: string): SubagentParamsLike {
|
|
161
|
+
const mode = resolveMode(params);
|
|
162
|
+
const tasks = resolveTasks(params);
|
|
163
|
+
const names = agentNameSet(rootCwd);
|
|
164
|
+
if (!mode) throw new Error("Provide exactly one mode: agent/task, tasks, or chain.");
|
|
165
|
+
|
|
166
|
+
const base = {
|
|
167
|
+
agentScope: params.agentScope ?? "both",
|
|
168
|
+
cwd: rootCwd,
|
|
169
|
+
context: "fresh" as const,
|
|
170
|
+
async: false,
|
|
171
|
+
clarify: false,
|
|
172
|
+
includeProgress: true,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (mode === "single") {
|
|
176
|
+
const task = tasks[0]!;
|
|
177
|
+
const mapped = mapSingleTask(task, names);
|
|
178
|
+
return {
|
|
179
|
+
...base,
|
|
180
|
+
agent: mapped.agent,
|
|
181
|
+
task: mapped.task,
|
|
182
|
+
cwd: task.cwd ? path.resolve(rootCwd, task.cwd) : rootCwd,
|
|
183
|
+
model: mapped.model,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (mode === "parallel") {
|
|
188
|
+
return {
|
|
189
|
+
...base,
|
|
190
|
+
tasks: tasks.map((task) => mapSingleTask(task, names)),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...base,
|
|
196
|
+
chain: tasks.map((task) => {
|
|
197
|
+
const mapped = mapSingleTask(task, names);
|
|
198
|
+
return {
|
|
199
|
+
agent: mapped.agent,
|
|
200
|
+
task: mapped.task,
|
|
201
|
+
cwd: task.cwd,
|
|
202
|
+
model: mapped.model,
|
|
203
|
+
};
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function createTakomiPiSubagentsEngine(pi: ExtensionAPI) {
|
|
209
|
+
const state = createState();
|
|
210
|
+
const config: ExtensionConfig = {
|
|
211
|
+
maxSubagentDepth: 2,
|
|
212
|
+
asyncByDefault: false,
|
|
213
|
+
forceTopLevelAsync: false,
|
|
214
|
+
};
|
|
215
|
+
const executor = createSubagentExecutor({
|
|
216
|
+
pi,
|
|
217
|
+
state,
|
|
218
|
+
config,
|
|
219
|
+
asyncByDefault: false,
|
|
220
|
+
tempArtifactsDir: TEMP_ARTIFACTS_DIR,
|
|
221
|
+
getSubagentSessionRoot,
|
|
222
|
+
expandTilde,
|
|
223
|
+
discoverAgents: discoverUnifiedAgents,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
async execute(
|
|
228
|
+
id: string,
|
|
229
|
+
params: TakomiSubagentToolParams,
|
|
230
|
+
signal: AbortSignal | undefined,
|
|
231
|
+
onUpdate: ToolUpdate | undefined,
|
|
232
|
+
ctx: ExtensionContext,
|
|
233
|
+
): Promise<AgentToolResult<Details>> {
|
|
234
|
+
const rootCwd = params.cwd ? path.resolve(ctx.cwd, params.cwd) : ctx.cwd;
|
|
235
|
+
const subagentParams = toSubagentParams(params, rootCwd);
|
|
236
|
+
return executor.execute(id, subagentParams, signal ?? NEVER_ABORT, onUpdate, ctx);
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Centralizes Takomi's current pi-subagents internal imports.
|
|
2
|
+
// pi-subagents does not yet expose all of these helpers through a stable public API,
|
|
3
|
+
// so package.json pins the dependency exactly and this adapter localizes future
|
|
4
|
+
// upstream path changes to one file.
|
|
5
|
+
export { createSubagentExecutor } from "pi-subagents/src/runs/foreground/subagent-executor";
|
|
6
|
+
export type { SubagentParamsLike } from "pi-subagents/src/runs/foreground/subagent-executor";
|
|
7
|
+
export { discoverAgents as discoverPiAgents } from "pi-subagents/src/agents/agents";
|
|
8
|
+
export type { AgentConfig, AgentScope } from "pi-subagents/src/agents/agents";
|
|
9
|
+
export {
|
|
10
|
+
DEFAULT_ARTIFACT_CONFIG,
|
|
11
|
+
TEMP_ARTIFACTS_DIR,
|
|
12
|
+
} from "pi-subagents/src/shared/types";
|
|
13
|
+
export type {
|
|
14
|
+
Details,
|
|
15
|
+
ExtensionConfig,
|
|
16
|
+
SubagentState,
|
|
17
|
+
} from "pi-subagents/src/shared/types";
|
|
18
|
+
export { renderSubagentResult, syncResultAnimation } from "pi-subagents/src/tui/render";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "takomi",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "🎯 Stop wrestling with AI. Start building with purpose. The artisan's toolkit for agent workflows, Codex skills, and original Takomi capabilities like 21st.dev integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"commander": "^13.1.0",
|
|
42
42
|
"figlet": "^1.8.0",
|
|
43
43
|
"fs-extra": "^11.3.0",
|
|
44
|
-
"pi-subagents": "
|
|
44
|
+
"pi-subagents": "0.24.0",
|
|
45
45
|
"picocolors": "^1.1.1",
|
|
46
46
|
"prompts": "^2.4.2"
|
|
47
47
|
},
|
package/src/update-check.js
CHANGED
|
@@ -1,140 +1,144 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import https from 'https';
|
|
5
|
-
import { spawnSync } from 'child_process';
|
|
6
|
-
import pc from 'picocolors';
|
|
7
|
-
|
|
8
|
-
const CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9
|
-
const CACHE_PATH = path.join(os.homedir(), '.takomi', 'update-check.json');
|
|
10
|
-
const REGISTRY_URL = 'https://registry.npmjs.org/takomi/latest';
|
|
11
|
-
|
|
12
|
-
function parseVersion(version = '') {
|
|
13
|
-
const [core] = String(version).replace(/^v/, '').split('-');
|
|
14
|
-
return core.split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function isNewerVersion(latest, current) {
|
|
18
|
-
const a = parseVersion(latest);
|
|
19
|
-
const b = parseVersion(current);
|
|
20
|
-
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
21
|
-
const left = a[i] || 0;
|
|
22
|
-
const right = b[i] || 0;
|
|
23
|
-
if (left > right) return true;
|
|
24
|
-
if (left < right) return false;
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function fetchLatestPackageInfo(timeoutMs = 2500) {
|
|
30
|
-
return new Promise((resolve) => {
|
|
31
|
-
const req = https.get(REGISTRY_URL, {
|
|
32
|
-
headers: {
|
|
33
|
-
'accept': 'application/json',
|
|
34
|
-
'user-agent': 'takomi-update-check',
|
|
35
|
-
},
|
|
36
|
-
timeout: timeoutMs,
|
|
37
|
-
}, (res) => {
|
|
38
|
-
let body = '';
|
|
39
|
-
res.setEncoding('utf8');
|
|
40
|
-
res.on('data', (chunk) => { body += chunk; });
|
|
41
|
-
res.on('end', () => {
|
|
42
|
-
if (res.statusCode && res.statusCode >= 400) return resolve(null);
|
|
43
|
-
try {
|
|
44
|
-
resolve(JSON.parse(body));
|
|
45
|
-
} catch {
|
|
46
|
-
resolve(null);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
req.on('timeout', () => {
|
|
51
|
-
req.destroy();
|
|
52
|
-
resolve(null);
|
|
53
|
-
});
|
|
54
|
-
req.on('error', () => resolve(null));
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function readCache() {
|
|
59
|
-
try {
|
|
60
|
-
return await fs.readJson(CACHE_PATH);
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function writeCache(cache) {
|
|
67
|
-
try {
|
|
68
|
-
await fs.ensureDir(path.dirname(CACHE_PATH));
|
|
69
|
-
await fs.writeJson(CACHE_PATH, cache, { spaces: 2 });
|
|
70
|
-
} catch {
|
|
71
|
-
// Update checks must never block normal Takomi startup.
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function getLatestTakomiVersion({ currentVersion, force = false } = {}) {
|
|
76
|
-
const now = Date.now();
|
|
77
|
-
const cache = await readCache();
|
|
78
|
-
if (!force && cache?.checkedAt && now - cache.checkedAt < CHECK_INTERVAL_MS) {
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
if (result
|
|
120
|
-
console.log(pc.yellow(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.log(pc.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { spawnSync } from 'child_process';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
|
|
8
|
+
const CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9
|
+
const CACHE_PATH = path.join(os.homedir(), '.takomi', 'update-check.json');
|
|
10
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/takomi/latest';
|
|
11
|
+
|
|
12
|
+
function parseVersion(version = '') {
|
|
13
|
+
const [core] = String(version).replace(/^v/, '').split('-');
|
|
14
|
+
return core.split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isNewerVersion(latest, current) {
|
|
18
|
+
const a = parseVersion(latest);
|
|
19
|
+
const b = parseVersion(current);
|
|
20
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
21
|
+
const left = a[i] || 0;
|
|
22
|
+
const right = b[i] || 0;
|
|
23
|
+
if (left > right) return true;
|
|
24
|
+
if (left < right) return false;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function fetchLatestPackageInfo(timeoutMs = 2500) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const req = https.get(REGISTRY_URL, {
|
|
32
|
+
headers: {
|
|
33
|
+
'accept': 'application/json',
|
|
34
|
+
'user-agent': 'takomi-update-check',
|
|
35
|
+
},
|
|
36
|
+
timeout: timeoutMs,
|
|
37
|
+
}, (res) => {
|
|
38
|
+
let body = '';
|
|
39
|
+
res.setEncoding('utf8');
|
|
40
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
if (res.statusCode && res.statusCode >= 400) return resolve(null);
|
|
43
|
+
try {
|
|
44
|
+
resolve(JSON.parse(body));
|
|
45
|
+
} catch {
|
|
46
|
+
resolve(null);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
req.on('timeout', () => {
|
|
51
|
+
req.destroy();
|
|
52
|
+
resolve(null);
|
|
53
|
+
});
|
|
54
|
+
req.on('error', () => resolve(null));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readCache() {
|
|
59
|
+
try {
|
|
60
|
+
return await fs.readJson(CACHE_PATH);
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function writeCache(cache) {
|
|
67
|
+
try {
|
|
68
|
+
await fs.ensureDir(path.dirname(CACHE_PATH));
|
|
69
|
+
await fs.writeJson(CACHE_PATH, cache, { spaces: 2 });
|
|
70
|
+
} catch {
|
|
71
|
+
// Update checks must never block normal Takomi startup.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function getLatestTakomiVersion({ currentVersion, force = false } = {}) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const cache = await readCache();
|
|
78
|
+
if (!force && cache?.checkedAt && cache.latestVersion && now - cache.checkedAt < CHECK_INTERVAL_MS) {
|
|
79
|
+
return {
|
|
80
|
+
...cache,
|
|
81
|
+
currentVersion,
|
|
82
|
+
updateAvailable: Boolean(currentVersion && isNewerVersion(cache.latestVersion, currentVersion)),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const info = await fetchLatestPackageInfo();
|
|
87
|
+
const latestVersion = typeof info?.version === 'string' ? info.version : null;
|
|
88
|
+
const next = {
|
|
89
|
+
checkedAt: now,
|
|
90
|
+
currentVersion,
|
|
91
|
+
latestVersion,
|
|
92
|
+
updateAvailable: Boolean(latestVersion && currentVersion && isNewerVersion(latestVersion, currentVersion)),
|
|
93
|
+
};
|
|
94
|
+
await writeCache(next);
|
|
95
|
+
return next;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function notifyIfTakomiUpdateAvailable(currentVersion) {
|
|
99
|
+
if (process.env.TAKOMI_NO_UPDATE_CHECK === '1') return;
|
|
100
|
+
|
|
101
|
+
// Fire-and-forget by design: launching the Takomi harness must never wait on
|
|
102
|
+
// network, DNS, npm registry latency, or cache file IO.
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
getLatestTakomiVersion({ currentVersion })
|
|
105
|
+
.then((result) => {
|
|
106
|
+
if (!result?.updateAvailable) return;
|
|
107
|
+
console.log(pc.yellow(`\n⬆ Takomi ${result.latestVersion} is available (installed: ${currentVersion}).`));
|
|
108
|
+
console.log(pc.dim(' Run: takomi upgrade'));
|
|
109
|
+
console.log(pc.dim(' Disable this check with TAKOMI_NO_UPDATE_CHECK=1.\n'));
|
|
110
|
+
})
|
|
111
|
+
.catch(() => {
|
|
112
|
+
// Silent: update checks must never affect harness startup or usage.
|
|
113
|
+
});
|
|
114
|
+
}, 0).unref?.();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function printTakomiUpdateStatus(currentVersion) {
|
|
118
|
+
const result = await getLatestTakomiVersion({ currentVersion, force: true });
|
|
119
|
+
if (!result?.latestVersion) {
|
|
120
|
+
console.log(pc.yellow('Could not check the npm registry for Takomi updates.'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (result.updateAvailable) {
|
|
124
|
+
console.log(pc.yellow(`Takomi ${result.latestVersion} is available (installed: ${currentVersion}).`));
|
|
125
|
+
console.log(pc.dim('Run: takomi upgrade'));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log(pc.green(`Takomi is up to date (${currentVersion}).`));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function upgradeTakomiPackage() {
|
|
132
|
+
console.log(pc.cyan('Updating Takomi from npm...\n'));
|
|
133
|
+
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
134
|
+
const result = spawnSync(command, ['install', '-g', 'takomi@latest'], {
|
|
135
|
+
stdio: 'inherit',
|
|
136
|
+
shell: process.platform === 'win32',
|
|
137
|
+
});
|
|
138
|
+
if (result.status === 0) {
|
|
139
|
+
console.log(pc.green('\nTakomi updated. Run `takomi --version` to confirm.'));
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
console.log(pc.red('\nTakomi update failed. Try manually: npm install -g takomi@latest'));
|
|
143
|
+
return result.status || 1;
|
|
144
|
+
}
|