typeclaw 0.1.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/LICENSE +21 -0
- package/README.md +134 -0
- package/auth.schema.json +63 -0
- package/cron.schema.json +96 -0
- package/package.json +72 -0
- package/scripts/emit-base-dockerfile.ts +5 -0
- package/scripts/generate-schema.ts +34 -0
- package/secrets.schema.json +63 -0
- package/src/agent/auth.ts +119 -0
- package/src/agent/compaction.ts +35 -0
- package/src/agent/git-nudge.ts +95 -0
- package/src/agent/index.ts +451 -0
- package/src/agent/plugin-tools.ts +269 -0
- package/src/agent/reload-tool.ts +71 -0
- package/src/agent/self.ts +45 -0
- package/src/agent/session-origin.ts +288 -0
- package/src/agent/subagents.ts +253 -0
- package/src/agent/system-prompt.ts +68 -0
- package/src/agent/tools/channel-fetch-attachment.ts +118 -0
- package/src/agent/tools/channel-history.ts +119 -0
- package/src/agent/tools/channel-reply.ts +182 -0
- package/src/agent/tools/channel-send.ts +212 -0
- package/src/agent/tools/ddg.ts +218 -0
- package/src/agent/tools/restart.ts +122 -0
- package/src/agent/tools/stream-snapshot.ts +181 -0
- package/src/agent/tools/webfetch/fetch.ts +102 -0
- package/src/agent/tools/webfetch/index.ts +1 -0
- package/src/agent/tools/webfetch/strategies/grep.ts +70 -0
- package/src/agent/tools/webfetch/strategies/jq.ts +31 -0
- package/src/agent/tools/webfetch/strategies/raw.ts +3 -0
- package/src/agent/tools/webfetch/strategies/readability.ts +30 -0
- package/src/agent/tools/webfetch/strategies/selector.ts +41 -0
- package/src/agent/tools/webfetch/strategies/snapshot.ts +135 -0
- package/src/agent/tools/webfetch/tool.ts +281 -0
- package/src/agent/tools/webfetch/types.ts +33 -0
- package/src/agent/tools/websearch.ts +96 -0
- package/src/agent/tools/wikipedia.ts +52 -0
- package/src/bundled-plugins/agent-browser/dashboard-discovery.ts +170 -0
- package/src/bundled-plugins/agent-browser/dashboard-proxy.ts +421 -0
- package/src/bundled-plugins/agent-browser/index.ts +179 -0
- package/src/bundled-plugins/agent-browser/shim-install.ts +158 -0
- package/src/bundled-plugins/agent-browser/shim.ts +152 -0
- package/src/bundled-plugins/agent-browser/skills/agent-browser/SKILL.md +113 -0
- package/src/bundled-plugins/guard/index.ts +26 -0
- package/src/bundled-plugins/guard/policies/non-workspace-write.ts +98 -0
- package/src/bundled-plugins/guard/policies/skill-authoring.ts +185 -0
- package/src/bundled-plugins/guard/policies/uncommitted-changes.ts +85 -0
- package/src/bundled-plugins/guard/policy.ts +18 -0
- package/src/bundled-plugins/memory/README.md +71 -0
- package/src/bundled-plugins/memory/append-tool.ts +84 -0
- package/src/bundled-plugins/memory/dreaming-state.ts +86 -0
- package/src/bundled-plugins/memory/dreaming.ts +470 -0
- package/src/bundled-plugins/memory/fragment-parser.ts +67 -0
- package/src/bundled-plugins/memory/index.ts +238 -0
- package/src/bundled-plugins/memory/load-memory.ts +122 -0
- package/src/bundled-plugins/memory/memory-logger.ts +257 -0
- package/src/bundled-plugins/memory/secret-detector.ts +49 -0
- package/src/bundled-plugins/memory/watermark.ts +15 -0
- package/src/bundled-plugins/security/index.ts +35 -0
- package/src/bundled-plugins/security/policies/git-exfil.ts +120 -0
- package/src/bundled-plugins/security/policies/outbound-secret-scan.ts +167 -0
- package/src/bundled-plugins/security/policies/prompt-injection.ts +488 -0
- package/src/bundled-plugins/security/policies/secret-exfil-bash.ts +99 -0
- package/src/bundled-plugins/security/policies/secret-exfil-read.ts +127 -0
- package/src/bundled-plugins/security/policies/session-search-secrets.ts +86 -0
- package/src/bundled-plugins/security/policies/ssrf.ts +196 -0
- package/src/bundled-plugins/security/policies/system-prompt-leak.ts +81 -0
- package/src/bundled-plugins/security/policy.ts +9 -0
- package/src/channels/adapters/discord-bot-channel-resolver.ts +77 -0
- package/src/channels/adapters/discord-bot-classify.ts +148 -0
- package/src/channels/adapters/discord-bot.ts +640 -0
- package/src/channels/adapters/kakaotalk-author-resolver.ts +78 -0
- package/src/channels/adapters/kakaotalk-channel-resolver.ts +105 -0
- package/src/channels/adapters/kakaotalk-classify.ts +77 -0
- package/src/channels/adapters/kakaotalk.ts +622 -0
- package/src/channels/adapters/slack-bot-author-resolver.ts +80 -0
- package/src/channels/adapters/slack-bot-channel-resolver.ts +84 -0
- package/src/channels/adapters/slack-bot-classify.ts +213 -0
- package/src/channels/adapters/slack-bot-dedupe.ts +51 -0
- package/src/channels/adapters/slack-bot-time.ts +10 -0
- package/src/channels/adapters/slack-bot.ts +881 -0
- package/src/channels/adapters/telegram-bot-classify.ts +155 -0
- package/src/channels/adapters/telegram-bot-format.ts +309 -0
- package/src/channels/adapters/telegram-bot.ts +604 -0
- package/src/channels/engagement.ts +227 -0
- package/src/channels/index.ts +21 -0
- package/src/channels/manager.ts +292 -0
- package/src/channels/membership-cache.ts +116 -0
- package/src/channels/membership-from-history.ts +53 -0
- package/src/channels/membership.ts +30 -0
- package/src/channels/participants.ts +47 -0
- package/src/channels/persistence.ts +209 -0
- package/src/channels/reloadable.ts +28 -0
- package/src/channels/router.ts +1570 -0
- package/src/channels/schema.ts +273 -0
- package/src/channels/types.ts +160 -0
- package/src/cli/channel.ts +403 -0
- package/src/cli/compose-status.ts +95 -0
- package/src/cli/compose.ts +240 -0
- package/src/cli/hostd.ts +163 -0
- package/src/cli/index.ts +27 -0
- package/src/cli/init.ts +592 -0
- package/src/cli/logs.ts +38 -0
- package/src/cli/reload.ts +68 -0
- package/src/cli/restart.ts +66 -0
- package/src/cli/run.ts +77 -0
- package/src/cli/shell.ts +33 -0
- package/src/cli/start.ts +57 -0
- package/src/cli/status.ts +178 -0
- package/src/cli/stop.ts +31 -0
- package/src/cli/tui.ts +35 -0
- package/src/cli/ui.ts +110 -0
- package/src/commands/index.ts +74 -0
- package/src/compose/discover.ts +43 -0
- package/src/compose/index.ts +25 -0
- package/src/compose/logs.ts +162 -0
- package/src/compose/restart.ts +69 -0
- package/src/compose/start.ts +62 -0
- package/src/compose/status.ts +28 -0
- package/src/compose/stop.ts +43 -0
- package/src/config/config.ts +424 -0
- package/src/config/index.ts +25 -0
- package/src/config/providers.ts +234 -0
- package/src/config/reloadable.ts +47 -0
- package/src/container/index.ts +27 -0
- package/src/container/logs.ts +37 -0
- package/src/container/port.ts +137 -0
- package/src/container/shared.ts +290 -0
- package/src/container/shell.ts +58 -0
- package/src/container/start.ts +670 -0
- package/src/container/status.ts +76 -0
- package/src/container/stop.ts +120 -0
- package/src/container/verify-running.ts +149 -0
- package/src/cron/consumer.ts +138 -0
- package/src/cron/index.ts +54 -0
- package/src/cron/reloadable.ts +64 -0
- package/src/cron/scheduler.ts +200 -0
- package/src/cron/schema.ts +96 -0
- package/src/hostd/client.ts +113 -0
- package/src/hostd/daemon.ts +587 -0
- package/src/hostd/index.ts +25 -0
- package/src/hostd/paths.ts +82 -0
- package/src/hostd/portbroker-manager.ts +101 -0
- package/src/hostd/protocol.ts +48 -0
- package/src/hostd/spawn.ts +224 -0
- package/src/hostd/supervisor.ts +60 -0
- package/src/hostd/tailscale.ts +172 -0
- package/src/hostd/version.ts +115 -0
- package/src/init/dockerfile.ts +327 -0
- package/src/init/ensure-deps.ts +152 -0
- package/src/init/gitignore.ts +46 -0
- package/src/init/hatching.ts +60 -0
- package/src/init/index.ts +786 -0
- package/src/init/kakaotalk-auth.ts +114 -0
- package/src/init/models-dev.ts +130 -0
- package/src/init/oauth-login.ts +74 -0
- package/src/init/packagejson.ts +94 -0
- package/src/init/paths.ts +2 -0
- package/src/init/run-bun-install.ts +20 -0
- package/src/markdown/chunk.ts +299 -0
- package/src/markdown/index.ts +1 -0
- package/src/plugin/context.ts +40 -0
- package/src/plugin/define.ts +35 -0
- package/src/plugin/hooks.ts +204 -0
- package/src/plugin/index.ts +63 -0
- package/src/plugin/loader.ts +111 -0
- package/src/plugin/manager.ts +136 -0
- package/src/plugin/registry.ts +145 -0
- package/src/plugin/skills.ts +62 -0
- package/src/plugin/types.ts +172 -0
- package/src/portbroker/bind-with-forward.ts +102 -0
- package/src/portbroker/container-server.ts +305 -0
- package/src/portbroker/forward-result-bus.ts +36 -0
- package/src/portbroker/hostd-client.ts +443 -0
- package/src/portbroker/index.ts +33 -0
- package/src/portbroker/policy.ts +24 -0
- package/src/portbroker/proc-net-tcp.ts +72 -0
- package/src/portbroker/protocol.ts +39 -0
- package/src/reload/client.ts +59 -0
- package/src/reload/index.ts +3 -0
- package/src/reload/registry.ts +60 -0
- package/src/reload/types.ts +13 -0
- package/src/run/bundled-plugins.ts +24 -0
- package/src/run/channel-session-factory.ts +105 -0
- package/src/run/index.ts +432 -0
- package/src/run/plugin-runtime.ts +43 -0
- package/src/run/schema-with-plugins.ts +14 -0
- package/src/secrets/index.ts +13 -0
- package/src/secrets/migrate.ts +95 -0
- package/src/secrets/schema.ts +75 -0
- package/src/secrets/storage.ts +231 -0
- package/src/server/index.ts +436 -0
- package/src/sessions/index.ts +23 -0
- package/src/shared/index.ts +9 -0
- package/src/shared/local-time.ts +21 -0
- package/src/shared/protocol.ts +25 -0
- package/src/skills/typeclaw-channel-kakaotalk/SKILL.md +87 -0
- package/src/skills/typeclaw-channel-telegram-bot/SKILL.md +64 -0
- package/src/skills/typeclaw-config/SKILL.md +643 -0
- package/src/skills/typeclaw-cron/SKILL.md +159 -0
- package/src/skills/typeclaw-git/SKILL.md +89 -0
- package/src/skills/typeclaw-memory/SKILL.md +174 -0
- package/src/skills/typeclaw-monorepo/SKILL.md +175 -0
- package/src/skills/typeclaw-plugins/SKILL.md +594 -0
- package/src/skills/typeclaw-skills/SKILL.md +246 -0
- package/src/stream/broker.ts +161 -0
- package/src/stream/index.ts +16 -0
- package/src/stream/types.ts +69 -0
- package/src/tui/client.ts +45 -0
- package/src/tui/format.ts +317 -0
- package/src/tui/index.ts +225 -0
- package/src/tui/theme.ts +41 -0
- package/typeclaw.schema.json +826 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { containerNameFromCwd } from '@/container'
|
|
5
|
+
import { isInitialized } from '@/init'
|
|
6
|
+
|
|
7
|
+
export type AgentEntry = {
|
|
8
|
+
name: string
|
|
9
|
+
cwd: string
|
|
10
|
+
containerName: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// One-depth scan: lists immediate subdirectories of `rootCwd`, keeps the ones
|
|
14
|
+
// that contain a typeclaw.json, and skips dot-prefixed names (.git, .vscode,
|
|
15
|
+
// node_modules-style hidden dirs are not skipped because they don't match the
|
|
16
|
+
// dot-prefix rule, but they also won't pass the typeclaw.json filter).
|
|
17
|
+
//
|
|
18
|
+
// Returns an empty array when rootCwd doesn't exist or is empty — discovery is
|
|
19
|
+
// not the place to fail; the caller decides what to do with zero agents.
|
|
20
|
+
//
|
|
21
|
+
// Sort by name so output across operations (up/down/ps/restart/logs) is
|
|
22
|
+
// deterministic regardless of filesystem readdir order.
|
|
23
|
+
export function discoverAgents(rootCwd: string): AgentEntry[] {
|
|
24
|
+
const root = resolve(rootCwd)
|
|
25
|
+
let entries: { name: string; isDir: boolean }[]
|
|
26
|
+
try {
|
|
27
|
+
entries = readdirSync(root, { withFileTypes: true }).map((d) => ({ name: d.name, isDir: d.isDirectory() }))
|
|
28
|
+
} catch {
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const agents: AgentEntry[] = []
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (!entry.isDir) continue
|
|
35
|
+
if (entry.name.startsWith('.')) continue
|
|
36
|
+
const cwd = join(root, entry.name)
|
|
37
|
+
if (!isInitialized(cwd)) continue
|
|
38
|
+
agents.push({ name: entry.name, cwd, containerName: containerNameFromCwd(cwd) })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
agents.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
42
|
+
return agents
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { discoverAgents, type AgentEntry } from './discover'
|
|
2
|
+
export { colorFor, composeLogs, makeLinePrefixer, type ComposeLogsOptions, type ComposeLogsResult } from './logs'
|
|
3
|
+
export { composeStatus, type AgentRuntimeState, type AgentStatusEntry, type ComposeStatusResult } from './status'
|
|
4
|
+
export {
|
|
5
|
+
composeRestart,
|
|
6
|
+
type ComposeRestartEvent,
|
|
7
|
+
type ComposeRestartOptions,
|
|
8
|
+
type ComposeRestartResult,
|
|
9
|
+
type RestartData,
|
|
10
|
+
} from './restart'
|
|
11
|
+
export {
|
|
12
|
+
composeStart,
|
|
13
|
+
type AgentResult,
|
|
14
|
+
type ComposeStartEvent,
|
|
15
|
+
type ComposeStartOptions,
|
|
16
|
+
type ComposeStartResult,
|
|
17
|
+
type StartSuccess,
|
|
18
|
+
} from './start'
|
|
19
|
+
export {
|
|
20
|
+
composeStop,
|
|
21
|
+
type ComposeStopEvent,
|
|
22
|
+
type ComposeStopOptions,
|
|
23
|
+
type ComposeStopResult,
|
|
24
|
+
type StopSuccess,
|
|
25
|
+
} from './stop'
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { containerExists } from '@/container'
|
|
2
|
+
import { getBun } from '@/container/shared'
|
|
3
|
+
|
|
4
|
+
import { discoverAgents, type AgentEntry } from './discover'
|
|
5
|
+
|
|
6
|
+
export type ComposeLogsOptions = {
|
|
7
|
+
rootCwd: string
|
|
8
|
+
follow: boolean
|
|
9
|
+
out?: NodeJS.WritableStream
|
|
10
|
+
err?: NodeJS.WritableStream
|
|
11
|
+
signal?: AbortSignal
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ComposeLogsResult = {
|
|
15
|
+
agents: AgentEntry[]
|
|
16
|
+
attached: AgentEntry[]
|
|
17
|
+
missing: AgentEntry[]
|
|
18
|
+
exitCode: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const COLORS = ['36', '33', '32', '35', '34', '31', '96', '93', '92', '95'] as const
|
|
22
|
+
|
|
23
|
+
export function colorFor(name: string, palette: readonly string[] = COLORS): string {
|
|
24
|
+
let h = 0
|
|
25
|
+
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0
|
|
26
|
+
return palette[h % palette.length] ?? '0'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Stateful chunker that buffers partial lines across chunks: only emits
|
|
30
|
+
// newline-terminated lines (each prefixed with the agent name + bar), and
|
|
31
|
+
// flushes the un-terminated tail on EOF. Without this, interleaved chunks
|
|
32
|
+
// from multiple agents would shred lines mid-character.
|
|
33
|
+
export function makeLinePrefixer(
|
|
34
|
+
name: string,
|
|
35
|
+
width: number,
|
|
36
|
+
color: string,
|
|
37
|
+
useColor: boolean,
|
|
38
|
+
): { write: (chunk: string) => string; flush: () => string } {
|
|
39
|
+
const padded = name.padEnd(width)
|
|
40
|
+
const prefix = useColor ? `\x1b[${color}m${padded}\x1b[0m | ` : `${padded} | `
|
|
41
|
+
let buffer = ''
|
|
42
|
+
return {
|
|
43
|
+
write(chunk: string): string {
|
|
44
|
+
buffer += chunk
|
|
45
|
+
const nl = buffer.lastIndexOf('\n')
|
|
46
|
+
if (nl < 0) return ''
|
|
47
|
+
const complete = buffer.slice(0, nl + 1)
|
|
48
|
+
buffer = buffer.slice(nl + 1)
|
|
49
|
+
return complete
|
|
50
|
+
.split('\n')
|
|
51
|
+
.slice(0, -1)
|
|
52
|
+
.map((l) => `${prefix}${l}\n`)
|
|
53
|
+
.join('')
|
|
54
|
+
},
|
|
55
|
+
flush(): string {
|
|
56
|
+
if (buffer.length === 0) return ''
|
|
57
|
+
const out = `${prefix}${buffer}\n`
|
|
58
|
+
buffer = ''
|
|
59
|
+
return out
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function composeLogs({
|
|
65
|
+
rootCwd,
|
|
66
|
+
follow,
|
|
67
|
+
out = process.stdout,
|
|
68
|
+
err = process.stderr,
|
|
69
|
+
signal,
|
|
70
|
+
}: ComposeLogsOptions): Promise<ComposeLogsResult> {
|
|
71
|
+
const agents = discoverAgents(rootCwd)
|
|
72
|
+
|
|
73
|
+
const liveness = await Promise.all(
|
|
74
|
+
agents.map(async (a) => ({ agent: a, exists: await containerExists(a.containerName) })),
|
|
75
|
+
)
|
|
76
|
+
const attached = liveness.filter((l) => l.exists).map((l) => l.agent)
|
|
77
|
+
const missing = liveness.filter((l) => !l.exists).map((l) => l.agent)
|
|
78
|
+
|
|
79
|
+
for (const a of missing) {
|
|
80
|
+
err.write(`compose: skipping ${a.name} (container not running)\n`)
|
|
81
|
+
}
|
|
82
|
+
if (attached.length === 0) return { agents, attached, missing, exitCode: 0 }
|
|
83
|
+
|
|
84
|
+
const bun = getBun()
|
|
85
|
+
if (!bun) {
|
|
86
|
+
err.write('compose: bun runtime not available\n')
|
|
87
|
+
return { agents, attached, missing, exitCode: 1 }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const width = attached.reduce((w, a) => Math.max(w, a.name.length), 0)
|
|
91
|
+
const useColor = supportsColor(out)
|
|
92
|
+
|
|
93
|
+
const procs = attached.map((agent) => {
|
|
94
|
+
const cmd = follow ? ['docker', 'logs', '-f', agent.containerName] : ['docker', 'logs', agent.containerName]
|
|
95
|
+
const proc = bun.spawn({ cmd, stdout: 'pipe', stderr: 'pipe' })
|
|
96
|
+
return { agent, proc }
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const onAbort = (): void => {
|
|
100
|
+
for (const { proc } of procs) {
|
|
101
|
+
try {
|
|
102
|
+
proc.kill('SIGTERM')
|
|
103
|
+
} catch {
|
|
104
|
+
// already exited
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
109
|
+
|
|
110
|
+
const pumps = procs.flatMap(({ agent, proc }) => {
|
|
111
|
+
const color = colorFor(agent.name)
|
|
112
|
+
return [
|
|
113
|
+
pumpStream(proc.stdout, makeLinePrefixer(agent.name, width, color, useColor), out),
|
|
114
|
+
pumpStream(proc.stderr, makeLinePrefixer(agent.name, width, color, useColor), err),
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await Promise.all(pumps)
|
|
119
|
+
const exits = await Promise.all(procs.map((p) => p.proc.exited))
|
|
120
|
+
signal?.removeEventListener('abort', onAbort)
|
|
121
|
+
|
|
122
|
+
// 143 = 128 + SIGTERM(15). When we cancel via signal, every child exits 143;
|
|
123
|
+
// that's expected, not failure. Surface only the first non-OK, non-cancelled
|
|
124
|
+
// exit so a real `docker logs` failure (e.g. "no such container") still bubbles.
|
|
125
|
+
const exitCode = exits.find((c) => c !== 0 && c !== 143) ?? 0
|
|
126
|
+
return { agents, attached, missing, exitCode }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function pumpStream(
|
|
130
|
+
stream: ReadableStream<Uint8Array>,
|
|
131
|
+
prefixer: { write: (s: string) => string; flush: () => string },
|
|
132
|
+
sink: NodeJS.WritableStream,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const decoder = new TextDecoder()
|
|
135
|
+
const reader = stream.getReader()
|
|
136
|
+
try {
|
|
137
|
+
while (true) {
|
|
138
|
+
const { done, value } = await reader.read()
|
|
139
|
+
if (done) break
|
|
140
|
+
if (value && value.byteLength > 0) {
|
|
141
|
+
const out = prefixer.write(decoder.decode(value, { stream: true }))
|
|
142
|
+
if (out.length > 0) sink.write(out)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const tail = decoder.decode()
|
|
146
|
+
if (tail.length > 0) {
|
|
147
|
+
const out = prefixer.write(tail)
|
|
148
|
+
if (out.length > 0) sink.write(out)
|
|
149
|
+
}
|
|
150
|
+
const flushed = prefixer.flush()
|
|
151
|
+
if (flushed.length > 0) sink.write(flushed)
|
|
152
|
+
} finally {
|
|
153
|
+
reader.releaseLock()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function supportsColor(stream: NodeJS.WritableStream): boolean {
|
|
158
|
+
const tty = (stream as unknown as { isTTY?: boolean }).isTTY === true
|
|
159
|
+
if (!tty) return false
|
|
160
|
+
if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '') return false
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { validateConfig } from '@/config'
|
|
2
|
+
import { start, stop } from '@/container'
|
|
3
|
+
|
|
4
|
+
import { discoverAgents, type AgentEntry } from './discover'
|
|
5
|
+
import type { AgentResult, StartSuccess } from './start'
|
|
6
|
+
import type { StopSuccess } from './stop'
|
|
7
|
+
|
|
8
|
+
export type RestartData = { stop: StopSuccess; start: StartSuccess }
|
|
9
|
+
|
|
10
|
+
export type ComposeRestartEvent =
|
|
11
|
+
| { kind: 'agent-start'; name: string }
|
|
12
|
+
| { kind: 'agent-stopped'; name: string }
|
|
13
|
+
| { kind: 'agent-done'; name: string; result: AgentResult<RestartData> }
|
|
14
|
+
|
|
15
|
+
export type ComposeRestartOptions = {
|
|
16
|
+
rootCwd: string
|
|
17
|
+
preferredHostPort: number
|
|
18
|
+
forceBuild?: boolean
|
|
19
|
+
cliEntry?: string
|
|
20
|
+
onProgress?: (event: ComposeRestartEvent) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ComposeRestartResult = {
|
|
24
|
+
agents: AgentEntry[]
|
|
25
|
+
results: AgentResult<RestartData>[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function composeRestart({
|
|
29
|
+
rootCwd,
|
|
30
|
+
preferredHostPort,
|
|
31
|
+
forceBuild = false,
|
|
32
|
+
cliEntry,
|
|
33
|
+
onProgress,
|
|
34
|
+
}: ComposeRestartOptions): Promise<ComposeRestartResult> {
|
|
35
|
+
const agents = discoverAgents(rootCwd)
|
|
36
|
+
const results = await Promise.all(
|
|
37
|
+
agents.map(async (agent): Promise<AgentResult<RestartData>> => {
|
|
38
|
+
onProgress?.({ kind: 'agent-start', name: agent.name })
|
|
39
|
+
const result = await runOne(agent.name, agent.cwd, preferredHostPort, forceBuild, cliEntry, () => {
|
|
40
|
+
onProgress?.({ kind: 'agent-stopped', name: agent.name })
|
|
41
|
+
})
|
|
42
|
+
onProgress?.({ kind: 'agent-done', name: agent.name, result })
|
|
43
|
+
return result
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
return { agents, results }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runOne(
|
|
50
|
+
name: string,
|
|
51
|
+
cwd: string,
|
|
52
|
+
preferredHostPort: number,
|
|
53
|
+
forceBuild: boolean,
|
|
54
|
+
cliEntry: string | undefined,
|
|
55
|
+
onStopped: () => void,
|
|
56
|
+
): Promise<AgentResult<RestartData>> {
|
|
57
|
+
const validated = validateConfig(cwd)
|
|
58
|
+
if (!validated.ok) return { name, ok: false, reason: validated.reason }
|
|
59
|
+
try {
|
|
60
|
+
const stopped = await stop({ cwd })
|
|
61
|
+
if (!stopped.ok) return { name, ok: false, reason: stopped.reason }
|
|
62
|
+
onStopped()
|
|
63
|
+
const started = await start({ cwd, preferredHostPort, forceBuild, cliEntry })
|
|
64
|
+
if (!started.ok) return { name, ok: false, reason: started.reason }
|
|
65
|
+
return { name, ok: true, data: { stop: stopped, start: started } }
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return { name, ok: false, reason: error instanceof Error ? error.message : String(error) }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { validateConfig } from '@/config'
|
|
2
|
+
import { start, type StartResult } from '@/container'
|
|
3
|
+
|
|
4
|
+
import { discoverAgents, type AgentEntry } from './discover'
|
|
5
|
+
|
|
6
|
+
export type AgentResult<T> = { name: string; ok: true; data: T } | { name: string; ok: false; reason: string }
|
|
7
|
+
|
|
8
|
+
export type StartSuccess = Extract<StartResult, { ok: true }>
|
|
9
|
+
|
|
10
|
+
export type ComposeStartEvent =
|
|
11
|
+
| { kind: 'agent-start'; name: string }
|
|
12
|
+
| { kind: 'agent-done'; name: string; result: AgentResult<StartSuccess> }
|
|
13
|
+
|
|
14
|
+
export type ComposeStartOptions = {
|
|
15
|
+
rootCwd: string
|
|
16
|
+
preferredHostPort: number
|
|
17
|
+
forceBuild?: boolean
|
|
18
|
+
cliEntry?: string
|
|
19
|
+
onProgress?: (event: ComposeStartEvent) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ComposeStartResult = {
|
|
23
|
+
agents: AgentEntry[]
|
|
24
|
+
results: AgentResult<StartSuccess>[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function composeStart({
|
|
28
|
+
rootCwd,
|
|
29
|
+
preferredHostPort,
|
|
30
|
+
forceBuild = false,
|
|
31
|
+
cliEntry,
|
|
32
|
+
onProgress,
|
|
33
|
+
}: ComposeStartOptions): Promise<ComposeStartResult> {
|
|
34
|
+
const agents = discoverAgents(rootCwd)
|
|
35
|
+
const results = await Promise.all(
|
|
36
|
+
agents.map(async (agent): Promise<AgentResult<StartSuccess>> => {
|
|
37
|
+
onProgress?.({ kind: 'agent-start', name: agent.name })
|
|
38
|
+
const result = await runOne(agent.name, agent.cwd, preferredHostPort, forceBuild, cliEntry)
|
|
39
|
+
onProgress?.({ kind: 'agent-done', name: agent.name, result })
|
|
40
|
+
return result
|
|
41
|
+
}),
|
|
42
|
+
)
|
|
43
|
+
return { agents, results }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runOne(
|
|
47
|
+
name: string,
|
|
48
|
+
cwd: string,
|
|
49
|
+
preferredHostPort: number,
|
|
50
|
+
forceBuild: boolean,
|
|
51
|
+
cliEntry: string | undefined,
|
|
52
|
+
): Promise<AgentResult<StartSuccess>> {
|
|
53
|
+
const validated = validateConfig(cwd)
|
|
54
|
+
if (!validated.ok) return { name, ok: false, reason: validated.reason }
|
|
55
|
+
try {
|
|
56
|
+
const data = await start({ cwd, preferredHostPort, forceBuild, cliEntry })
|
|
57
|
+
if (!data.ok) return { name, ok: false, reason: data.reason }
|
|
58
|
+
return { name, ok: true, data }
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return { name, ok: false, reason: error instanceof Error ? error.message : String(error) }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { inspectContainer, resolveHostPort } from '@/container'
|
|
2
|
+
|
|
3
|
+
import { discoverAgents, type AgentEntry } from './discover'
|
|
4
|
+
|
|
5
|
+
export type AgentRuntimeState = 'running' | 'stopped' | 'absent'
|
|
6
|
+
|
|
7
|
+
export type AgentStatusEntry = AgentEntry & {
|
|
8
|
+
state: AgentRuntimeState
|
|
9
|
+
hostPort: number | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ComposeStatusResult = {
|
|
13
|
+
rootCwd: string
|
|
14
|
+
entries: AgentStatusEntry[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function composeStatus(rootCwd: string): Promise<ComposeStatusResult> {
|
|
18
|
+
const agents = discoverAgents(rootCwd)
|
|
19
|
+
const entries = await Promise.all(
|
|
20
|
+
agents.map(async (agent): Promise<AgentStatusEntry> => {
|
|
21
|
+
const container = await inspectContainer(agent.containerName)
|
|
22
|
+
const state: AgentRuntimeState = !container.exists ? 'absent' : container.running ? 'running' : 'stopped'
|
|
23
|
+
const hostPort = state === 'running' ? await resolveHostPort({ cwd: agent.cwd }).catch(() => null) : null
|
|
24
|
+
return { ...agent, state, hostPort }
|
|
25
|
+
}),
|
|
26
|
+
)
|
|
27
|
+
return { rootCwd, entries }
|
|
28
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { stop, type StopResult } from '@/container'
|
|
2
|
+
|
|
3
|
+
import { discoverAgents, type AgentEntry } from './discover'
|
|
4
|
+
import type { AgentResult } from './start'
|
|
5
|
+
|
|
6
|
+
export type StopSuccess = Extract<StopResult, { ok: true }>
|
|
7
|
+
|
|
8
|
+
export type ComposeStopEvent =
|
|
9
|
+
| { kind: 'agent-start'; name: string }
|
|
10
|
+
| { kind: 'agent-done'; name: string; result: AgentResult<StopSuccess> }
|
|
11
|
+
|
|
12
|
+
export type ComposeStopOptions = {
|
|
13
|
+
rootCwd: string
|
|
14
|
+
onProgress?: (event: ComposeStopEvent) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ComposeStopResult = {
|
|
18
|
+
agents: AgentEntry[]
|
|
19
|
+
results: AgentResult<StopSuccess>[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function composeStop({ rootCwd, onProgress }: ComposeStopOptions): Promise<ComposeStopResult> {
|
|
23
|
+
const agents = discoverAgents(rootCwd)
|
|
24
|
+
const results = await Promise.all(
|
|
25
|
+
agents.map(async (agent): Promise<AgentResult<StopSuccess>> => {
|
|
26
|
+
onProgress?.({ kind: 'agent-start', name: agent.name })
|
|
27
|
+
const result = await runOne(agent.name, agent.cwd)
|
|
28
|
+
onProgress?.({ kind: 'agent-done', name: agent.name, result })
|
|
29
|
+
return result
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
return { agents, results }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runOne(name: string, cwd: string): Promise<AgentResult<StopSuccess>> {
|
|
36
|
+
try {
|
|
37
|
+
const data = await stop({ cwd })
|
|
38
|
+
if (!data.ok) return { name, ok: false, reason: data.reason }
|
|
39
|
+
return { name, ok: true, data }
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return { name, ok: false, reason: error instanceof Error ? error.message : String(error) }
|
|
42
|
+
}
|
|
43
|
+
}
|