chamba 0.3.1 → 0.5.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 +47 -44
- package/bin/chamba.js +212 -0
- package/dist/commands/advanced.js +278 -0
- package/dist/commands/dev.js +612 -0
- package/dist/commands/doctor.js +29 -0
- package/dist/commands/menu.js +80 -0
- package/dist/commands/onboard.js +229 -0
- package/dist/commands/settings.js +349 -0
- package/dist/lib/agent-context.js +177 -0
- package/dist/lib/browser.js +40 -0
- package/dist/lib/chamba-yaml.js +191 -0
- package/dist/lib/constants.js +125 -0
- package/dist/lib/dockerfile-builder.js +267 -0
- package/dist/lib/env.js +78 -0
- package/dist/lib/global-config.js +66 -0
- package/dist/lib/pnpm-store.js +19 -0
- package/dist/lib/ports.js +210 -0
- package/dist/lib/safe-rm.js +26 -0
- package/dist/lib/sessions.js +34 -0
- package/dist/lib/shadows.js +174 -0
- package/dist/lib/webterm.js +305 -0
- package/dist/lib/workspace-identity.js +260 -0
- package/package.json +61 -23
- package/schema/chamba.schema.json +65 -0
- package/templates/.dockerignore +3 -0
- package/templates/Dockerfile +173 -0
- package/templates/claude-statusline.sh +120 -0
- package/templates/context/baseline.md +13 -0
- package/templates/context/context-usage.md +1 -0
- package/templates/context/git-mode-local.md +1 -0
- package/templates/context/git-mode-strict.md +1 -0
- package/templates/context/git-mode-unrestricted.md +1 -0
- package/templates/context/git-unavailable.md +1 -0
- package/templates/context/shadow-paths.md +3 -0
- package/templates/context-usage.sh +249 -0
- package/templates/git-readonly-wrapper.mjs +309 -0
- package/templates/npmrc +2 -0
- package/templates/pnpm-config.yaml +9 -0
- package/templates/runtime-constants.mjs +18 -0
- package/templates/skills/chamba-statusline/SKILL.md +79 -0
- package/templates/skills/context-usage/SKILL.md +53 -0
- package/templates/skills/web-pane/SKILL.md +62 -0
- package/templates/startup-git-mode.mjs +145 -0
- package/templates/startup.mjs +333 -0
- package/templates/webpane.sh +126 -0
- package/templates/webterm/README.md +160 -0
- package/templates/webterm/artifacts.js +583 -0
- package/templates/webterm/config.js +270 -0
- package/templates/webterm/context/claude.md +14 -0
- package/templates/webterm/conversation.js +253 -0
- package/templates/webterm/package-lock.json +884 -0
- package/templates/webterm/package.json +17 -0
- package/templates/webterm/pane.js +156 -0
- package/templates/webterm/proc.js +89 -0
- package/templates/webterm/public/app/alerts.js +472 -0
- package/templates/webterm/public/app/cards.js +123 -0
- package/templates/webterm/public/app/clipboard.js +229 -0
- package/templates/webterm/public/app/composer.js +226 -0
- package/templates/webterm/public/app/connection.js +342 -0
- package/templates/webterm/public/app/dictation.js +98 -0
- package/templates/webterm/public/app/dom.js +37 -0
- package/templates/webterm/public/app/drafts.js +244 -0
- package/templates/webterm/public/app/frames.js +166 -0
- package/templates/webterm/public/app/main.js +82 -0
- package/templates/webterm/public/app/new-session.js +188 -0
- package/templates/webterm/public/app/note.js +24 -0
- package/templates/webterm/public/app/pane-frame.js +166 -0
- package/templates/webterm/public/app/pane.js +353 -0
- package/templates/webterm/public/app/state.js +51 -0
- package/templates/webterm/public/app/status-strip.js +170 -0
- package/templates/webterm/public/app/tabs.js +475 -0
- package/templates/webterm/public/app/terminal.js +102 -0
- package/templates/webterm/public/app/theme.js +46 -0
- package/templates/webterm/public/favicon.svg +21 -0
- package/templates/webterm/public/index.html +105 -0
- package/templates/webterm/public/styles.css +1193 -0
- package/templates/webterm/resume.js +145 -0
- package/templates/webterm/server.js +1181 -0
- package/templates/webterm/sessions.js +515 -0
- package/templates/webterm/snapshot.js +135 -0
- package/templates/webterm.sh +164 -0
- package/dist/cli.js +0 -1582
- package/dist/server.js +0 -1831
- package/inject/annotate.js +0 -18
- package/skill/README.md +0 -12
- package/skill/SKILL.md +0 -93
- package/web/assets/highlighted-body-OFNGDK62-Bn4Eu7CG.js +0 -1
- package/web/assets/index-B9DI4F1Z.js +0 -202
- package/web/assets/index-DK_n6CTo.css +0 -2
- package/web/assets/mermaid-GHXKKRXX-CEMduc-U.js +0 -1
- package/web/index.html +0 -28
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// resume.js - The command that reopens the conversation this workspace was last having.
|
|
2
|
+
//
|
|
3
|
+
// A container you come back to should open where you left off, so the first session of a container start
|
|
4
|
+
// continues the newest conversation instead of beginning one. Working out which conversation that is means
|
|
5
|
+
// reading the store the agent keeps it in, and this is the only place that can honestly do it: the stores
|
|
6
|
+
// are the container's own paths, and the CLIs that write them are installed here and nowhere else.
|
|
7
|
+
//
|
|
8
|
+
// Deliberately next to conversation.js, and deliberately not the same question. That file asks which
|
|
9
|
+
// conversation a live session is having, and answers it from the process tree and the session's start time.
|
|
10
|
+
// This one asks whether there is anything here worth reopening at all, before any session exists, so the
|
|
11
|
+
// directory is the only thing it has to go on.
|
|
12
|
+
//
|
|
13
|
+
// Null is the half that matters. Every one of these CLIs treats "resume, but there is nothing there" as an
|
|
14
|
+
// error and exits, so a resume command with nothing behind it opens the browser onto a session that died on
|
|
15
|
+
// arrival. Null means the session starts the agent fresh, which is the right answer for a workspace with no
|
|
16
|
+
// history.
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { basename, dirname } from "node:path";
|
|
20
|
+
import { CLAUDE_TRANSCRIPT, CODEX_ROLLOUT, filesNewestFirst } from "./conversation.js";
|
|
21
|
+
|
|
22
|
+
// --- Claude ------------------------------------------------------------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Claude Code files its transcripts per directory, in a dir named after the directory it ran in with every
|
|
26
|
+
* character that is not a letter or a digit turned into "-": /workspace -> -workspace, /workspace/apps/api ->
|
|
27
|
+
* -workspace-apps-api. So a session in a sub-directory reads and writes a different dir from one at the root,
|
|
28
|
+
* and a resume command built from the wrong one names an id that claude cannot find.
|
|
29
|
+
* Only the fallback below relies on this naming; the main path reads what a transcript says about itself.
|
|
30
|
+
*/
|
|
31
|
+
export function claudeProjectKey(containerPath) {
|
|
32
|
+
return containerPath.replace(/[^a-zA-Z0-9]/g, "-");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The most recent claude conversation that can be reopened in `cwd`: the newest transcript with a real
|
|
37
|
+
* (non-sidechain) user message that ran in that directory. Returns null when nothing qualifies.
|
|
38
|
+
*
|
|
39
|
+
* The directory is matched on what the transcript records, not on which dir it sits in, so however claude
|
|
40
|
+
* names those dirs we never hand back a conversation from somewhere else - resuming it would either fail or
|
|
41
|
+
* drop the user into another directory's history. Transcripts old enough to record no directory at all are
|
|
42
|
+
* the fallback, and for those the dir name is the only evidence there is.
|
|
43
|
+
*/
|
|
44
|
+
function claudeResumeId(cwd, stores) {
|
|
45
|
+
const transcripts = filesNewestFirst(stores.claudeProjects, CLAUDE_TRANSCRIPT);
|
|
46
|
+
const byRecordedDir = firstResumable(transcripts, cwd);
|
|
47
|
+
if (byRecordedDir !== null) return byRecordedDir;
|
|
48
|
+
const key = claudeProjectKey(cwd);
|
|
49
|
+
return firstResumable(
|
|
50
|
+
transcripts.filter((path) => basename(dirname(path)) === key),
|
|
51
|
+
null,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The first of these transcripts that can be reopened, in the order given. `requiredCwd` of null asks only
|
|
56
|
+
// that the conversation has messages; a directory asks that it recorded that one too. Transcripts are read
|
|
57
|
+
// in order and the walk stops at the first hit, so the usual case reads a single file.
|
|
58
|
+
function firstResumable(transcriptPaths, requiredCwd) {
|
|
59
|
+
for (const path of transcriptPaths) {
|
|
60
|
+
const facts = transcriptFacts(path);
|
|
61
|
+
if (!facts.resumable) continue;
|
|
62
|
+
if (requiredCwd !== null && facts.cwd !== requiredCwd) continue;
|
|
63
|
+
return basename(path).slice(0, -".jsonl".length);
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What a transcript says about itself, in one pass: whether it holds a real user message, and the directory
|
|
70
|
+
* that message was typed in. Sessions that only recorded UI state (mode changes, slash commands) have
|
|
71
|
+
* nothing to resume; sidechain records belong to subagents. isSidechain is a field we do not own, so only an
|
|
72
|
+
* explicit true excludes a record: a record that stopped carrying the field still counts as resumable, which
|
|
73
|
+
* fails on the safe side. `cwd` is null for transcripts written before claude recorded it.
|
|
74
|
+
*
|
|
75
|
+
* Read whole rather than by the head, unlike conversation.js: the first user message can sit behind any
|
|
76
|
+
* amount of session setup, and answering "resumable" wrongly costs a dead session rather than a retry. The
|
|
77
|
+
* walk stops at the first hit, and the caller stops at the first resumable file, so the usual cost is the
|
|
78
|
+
* first few kilobytes of one transcript.
|
|
79
|
+
*/
|
|
80
|
+
function transcriptFacts(transcriptPath) {
|
|
81
|
+
let content;
|
|
82
|
+
try {
|
|
83
|
+
content = readFileSync(transcriptPath, "utf8");
|
|
84
|
+
} catch {
|
|
85
|
+
return { resumable: false, cwd: null };
|
|
86
|
+
}
|
|
87
|
+
// Walked by index rather than split("\n") so a multi-MB transcript is not copied into an array of lines.
|
|
88
|
+
let start = 0;
|
|
89
|
+
while (start <= content.length) {
|
|
90
|
+
const end = content.indexOf("\n", start);
|
|
91
|
+
const line = content.slice(start, end === -1 ? content.length : end);
|
|
92
|
+
start = (end === -1 ? content.length : end) + 1;
|
|
93
|
+
// Cheap pre-filter so multi-MB transcripts are not JSON.parsed line by line.
|
|
94
|
+
if (!line.includes('"type":"user"')) continue;
|
|
95
|
+
try {
|
|
96
|
+
const record = JSON.parse(line);
|
|
97
|
+
if (record.type === "user" && record.isSidechain !== true) {
|
|
98
|
+
return { resumable: true, cwd: typeof record.cwd === "string" ? record.cwd : null };
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Skip malformed lines.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { resumable: false, cwd: null };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- Codex and opencode ------------------------------------------------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
// What reopens a stored conversation, per agent, and where to look to know there is one. claude is not here:
|
|
110
|
+
// it is resumed by id above, because `claude --continue` takes the newest transcript by mtime and silently
|
|
111
|
+
// starts FRESH when that transcript holds no real messages, which makes auto-resume fail quietly whenever
|
|
112
|
+
// such a session happens to be the newest.
|
|
113
|
+
//
|
|
114
|
+
// These two are asked a shallower question than claude - does the store hold a conversation file at all -
|
|
115
|
+
// and their own flag picks which one. Reading each store's format to find out whether the newest
|
|
116
|
+
// conversation is really resumable would tie chamba to two more internal layouts for no gain.
|
|
117
|
+
export const CONTINUE = {
|
|
118
|
+
opencode: { argv: ["opencode", "--continue"], store: "opencodeSessions", pattern: /\.json$/ },
|
|
119
|
+
codex: { argv: ["codex", "resume", "--last"], store: "codexSessions", pattern: CODEX_ROLLOUT },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// --- The one entry point -----------------------------------------------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The argv that reopens this workspace's most recent conversation for `agent` in `cwd`, or null when there
|
|
126
|
+
* is nothing to reopen. `stores` is config.js's AGENT_STORES, passed in so this can be exercised against a
|
|
127
|
+
* fixture the way conversation.js is.
|
|
128
|
+
*
|
|
129
|
+
* An agent nobody wrote a reader for is a supported answer, not an error: it starts fresh.
|
|
130
|
+
*/
|
|
131
|
+
export function resumeArgvFor(agent, cwd, stores) {
|
|
132
|
+
try {
|
|
133
|
+
if (agent === "claude") {
|
|
134
|
+
const id = claudeResumeId(cwd, stores);
|
|
135
|
+
return id === null ? null : ["claude", "--resume", id];
|
|
136
|
+
}
|
|
137
|
+
const known = CONTINUE[agent];
|
|
138
|
+
if (!known) return null;
|
|
139
|
+
return filesNewestFirst(stores[known.store], known.pattern).length > 0 ? [...known.argv] : null;
|
|
140
|
+
} catch {
|
|
141
|
+
// A store that changed shape under us must never take the session down with it - starting fresh is
|
|
142
|
+
// a worse session than the user asked for, but it is a session.
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|