jeo-code 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/README.md +342 -0
- package/package.json +57 -0
- package/scripts/install.sh +322 -0
- package/scripts/uninstall.sh +30 -0
- package/src/agent/compaction.ts +75 -0
- package/src/agent/config-schema.ts +87 -0
- package/src/agent/context-files.ts +51 -0
- package/src/agent/engine.ts +208 -0
- package/src/agent/json.ts +87 -0
- package/src/agent/loop.ts +22 -0
- package/src/agent/session.ts +198 -0
- package/src/agent/state.ts +199 -0
- package/src/agent/subagents.ts +149 -0
- package/src/agent/tools.ts +355 -0
- package/src/ai/index.ts +11 -0
- package/src/ai/model-catalog-compat.ts +119 -0
- package/src/ai/model-catalog.ts +97 -0
- package/src/ai/model-discovery.ts +148 -0
- package/src/ai/model-enrich.ts +75 -0
- package/src/ai/model-manager.ts +178 -0
- package/src/ai/model-picker.ts +73 -0
- package/src/ai/model-registry.ts +83 -0
- package/src/ai/provider-status.ts +77 -0
- package/src/ai/providers/anthropic.ts +87 -0
- package/src/ai/providers/errors.ts +47 -0
- package/src/ai/providers/gemini.ts +77 -0
- package/src/ai/providers/ollama.ts +54 -0
- package/src/ai/providers/openai.ts +67 -0
- package/src/ai/sse.ts +46 -0
- package/src/ai/types.ts +37 -0
- package/src/auth/callback-server.ts +195 -0
- package/src/auth/flows/anthropic.ts +114 -0
- package/src/auth/flows/google.ts +120 -0
- package/src/auth/flows/index.ts +50 -0
- package/src/auth/flows/openai.ts +130 -0
- package/src/auth/index.ts +23 -0
- package/src/auth/oauth.ts +80 -0
- package/src/auth/pkce.ts +24 -0
- package/src/auth/refresh.ts +60 -0
- package/src/auth/storage.ts +113 -0
- package/src/auth/types.ts +26 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/runner.ts +245 -0
- package/src/cli.ts +17 -0
- package/src/commands/approve.ts +63 -0
- package/src/commands/auth.ts +144 -0
- package/src/commands/chat.ts +37 -0
- package/src/commands/deep-interview.ts +239 -0
- package/src/commands/doctor.ts +250 -0
- package/src/commands/evolve.ts +191 -0
- package/src/commands/launch.ts +745 -0
- package/src/commands/mcp.ts +18 -0
- package/src/commands/models.ts +104 -0
- package/src/commands/ralplan.ts +86 -0
- package/src/commands/resume.ts +6 -0
- package/src/commands/setup-helpers.ts +93 -0
- package/src/commands/setup.ts +190 -0
- package/src/commands/skills.ts +38 -0
- package/src/commands/team.ts +337 -0
- package/src/commands/ultragoal.ts +102 -0
- package/src/index.ts +31 -0
- package/src/mcp/index.ts +3 -0
- package/src/mcp/protocol.ts +45 -0
- package/src/mcp/server.ts +97 -0
- package/src/mcp/tools.ts +156 -0
- package/src/skills/catalog.ts +61 -0
- package/src/tui/app.ts +297 -0
- package/src/tui/components/ascii-art.ts +340 -0
- package/src/tui/components/autocomplete.ts +165 -0
- package/src/tui/components/capability.ts +29 -0
- package/src/tui/components/code-view.ts +146 -0
- package/src/tui/components/color.ts +172 -0
- package/src/tui/components/config-panel.ts +193 -0
- package/src/tui/components/evolution.ts +305 -0
- package/src/tui/components/footer.ts +95 -0
- package/src/tui/components/forge.ts +167 -0
- package/src/tui/components/index.ts +7 -0
- package/src/tui/components/layout.ts +105 -0
- package/src/tui/components/meter.ts +61 -0
- package/src/tui/components/model-picker.ts +82 -0
- package/src/tui/components/provider-picker.ts +42 -0
- package/src/tui/components/select-list.ts +199 -0
- package/src/tui/components/slash.ts +34 -0
- package/src/tui/components/spinner.ts +49 -0
- package/src/tui/components/status.ts +45 -0
- package/src/tui/components/stream.ts +36 -0
- package/src/tui/components/themes.ts +86 -0
- package/src/tui/components/tool-list.ts +67 -0
- package/src/tui/index.ts +2 -0
- package/src/tui/renderer.ts +70 -0
- package/src/tui/terminal.ts +78 -0
- package/src/util/retry.ts +108 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# joc (jeo-code) installer — gjc-style bun global install.
|
|
3
|
+
#
|
|
4
|
+
# The gjc parity path is a single Bun global install. The published-package form
|
|
5
|
+
# (once jeo-code is on npm) is identical to gajae-code's:
|
|
6
|
+
#
|
|
7
|
+
# bun install -g jeo-code # gjc parity: bun install -g gajae-code
|
|
8
|
+
#
|
|
9
|
+
# Until then this script performs the equivalent global install straight from the
|
|
10
|
+
# GitHub repo, auto-installing Bun if missing. Registry flags are explicit and
|
|
11
|
+
# safe by default: --registry is one-shot for this install; --persist-registry is
|
|
12
|
+
# required before npm's global config is changed.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# curl -fsSL <raw-url>/scripts/install.sh | sh # git install from GitHub URL
|
|
16
|
+
# sh scripts/install.sh # same as above
|
|
17
|
+
# sh scripts/install.sh --repo https://github.com/akillness/jeo-code.git
|
|
18
|
+
# sh scripts/install.sh --npm --registry https://registry.npmjs.org/
|
|
19
|
+
# sh scripts/install.sh --npm --registry https://npmjs.co.kr
|
|
20
|
+
# sh scripts/install.sh --registry https://your-company-registry.com --persist-registry
|
|
21
|
+
# sh scripts/install.sh --scope @my-org --registry https://your-company-registry.com --project-npmrc
|
|
22
|
+
# sh scripts/install.sh --ref v0.1.0 # global install of a specific git ref
|
|
23
|
+
# sh scripts/install.sh --local # dev: install from this clone (bun link)
|
|
24
|
+
# sh scripts/install.sh --binary # compile a standalone binary (no bun at runtime)
|
|
25
|
+
set -e
|
|
26
|
+
|
|
27
|
+
DEFAULT_REPO="https://github.com/akillness/jeo-code.git"
|
|
28
|
+
REPO="${JOC_REPO:-${JOC_REPO_URL:-$DEFAULT_REPO}}"
|
|
29
|
+
PKG="${JOC_PKG:-jeo-code}"
|
|
30
|
+
INSTALL_DIR="${JOC_INSTALL_DIR:-$HOME/.local/bin}"
|
|
31
|
+
MIN_BUN_VERSION="1.3.14"
|
|
32
|
+
|
|
33
|
+
MODE="global"
|
|
34
|
+
REF=""
|
|
35
|
+
SRC_DIR=""
|
|
36
|
+
LINKED=""
|
|
37
|
+
REGISTRY="${JOC_REGISTRY:-}"
|
|
38
|
+
SCOPE="${JOC_REGISTRY_SCOPE:-}"
|
|
39
|
+
PERSIST_REGISTRY=0
|
|
40
|
+
PROJECT_NPMRC=0
|
|
41
|
+
DRY_RUN=0
|
|
42
|
+
|
|
43
|
+
usage() {
|
|
44
|
+
cat <<EOF
|
|
45
|
+
joc installer (gjc-style Bun global install)
|
|
46
|
+
(default) bun global install from $REPO → exposes 'joc'
|
|
47
|
+
--repo <url|owner/repo> git source (default $DEFAULT_REPO)
|
|
48
|
+
--npm bun install -g $PKG (npm registry; gjc parity once published)
|
|
49
|
+
--package <name> npm package name for --npm (default $PKG)
|
|
50
|
+
--registry <url> one-shot registry for this install (does not mutate npm config)
|
|
51
|
+
--scope <@scope> registry scope key for persisted/project .npmrc config
|
|
52
|
+
--persist-registry run 'npm config set registry <url>' (or '<scope>:registry')
|
|
53
|
+
--project-npmrc write registry=<url> (or <scope>:registry=<url>) to ./.npmrc
|
|
54
|
+
--print-registry run 'npm config get registry' (or '<scope>:registry') and exit
|
|
55
|
+
--delete-registry run 'npm config delete registry' (or '<scope>:registry') and exit
|
|
56
|
+
--local install from current clone via 'bun link' (dev)
|
|
57
|
+
--binary compile a standalone binary (no bun needed at runtime)
|
|
58
|
+
--ref <ref> install a specific tag/branch/commit
|
|
59
|
+
--dry-run print the bun/npm commands without installing
|
|
60
|
+
Environment:
|
|
61
|
+
JOC_INSTALL_DIR (default \$HOME/.local/bin — compatibility symlink)
|
|
62
|
+
JOC_REPO/JOC_REPO_URL(default $DEFAULT_REPO)
|
|
63
|
+
JOC_PKG (default $PKG)
|
|
64
|
+
JOC_REGISTRY one-shot or persisted registry URL
|
|
65
|
+
JOC_REGISTRY_SCOPE optional scope such as @my-org
|
|
66
|
+
EOF
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
while [ $# -gt 0 ]; do
|
|
70
|
+
case "$1" in
|
|
71
|
+
--global) MODE="global"; shift ;;
|
|
72
|
+
--npm) MODE="npm"; shift ;;
|
|
73
|
+
--local) MODE="local"; shift ;;
|
|
74
|
+
--binary) MODE="binary"; shift ;;
|
|
75
|
+
--repo) shift; REPO="$1"; shift ;;
|
|
76
|
+
--repo=*) REPO="${1#*=}"; shift ;;
|
|
77
|
+
--package) shift; PKG="$1"; shift ;;
|
|
78
|
+
--package=*) PKG="${1#*=}"; shift ;;
|
|
79
|
+
--registry|--npm-registry) shift; REGISTRY="$1"; shift ;;
|
|
80
|
+
--registry=*|--npm-registry=*) REGISTRY="${1#*=}"; shift ;;
|
|
81
|
+
--scope) shift; SCOPE="$1"; shift ;;
|
|
82
|
+
--scope=*) SCOPE="${1#*=}"; shift ;;
|
|
83
|
+
--persist-registry) PERSIST_REGISTRY=1; shift ;;
|
|
84
|
+
--project-npmrc) PROJECT_NPMRC=1; shift ;;
|
|
85
|
+
--print-registry) MODE="registry-print"; shift ;;
|
|
86
|
+
--delete-registry) MODE="registry-delete"; shift ;;
|
|
87
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
88
|
+
--ref) shift; REF="$1"; shift ;;
|
|
89
|
+
--ref=*) REF="${1#*=}"; shift ;;
|
|
90
|
+
-r) shift; REF="$1"; shift ;;
|
|
91
|
+
-h|--help) usage; exit 0 ;;
|
|
92
|
+
*)
|
|
93
|
+
echo "Unknown option: $1"
|
|
94
|
+
exit 1 ;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
has_bun() { command -v bun >/dev/null 2>&1; }
|
|
99
|
+
has_npm() { command -v npm >/dev/null 2>&1; }
|
|
100
|
+
|
|
101
|
+
version_ge() {
|
|
102
|
+
cur="$1"; min="$2"
|
|
103
|
+
cm="${cur%%.*}"; rest="${cur#*.}"; cn="${rest%%.*}"; cp="${rest#*.}"; cp="${cp%%.*}"
|
|
104
|
+
mm="${min%%.*}"; rest="${min#*.}"; mn="${rest%%.*}"; mp="${rest#*.}"; mp="${mp%%.*}"
|
|
105
|
+
[ "$cm" -ne "$mm" ] && { [ "$cm" -gt "$mm" ]; return $?; }
|
|
106
|
+
[ "$cn" -ne "$mn" ] && { [ "$cn" -gt "$mn" ]; return $?; }
|
|
107
|
+
[ "$cp" -ge "$mp" ]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
require_bun() {
|
|
111
|
+
if ! has_bun; then
|
|
112
|
+
echo "Installing Bun (required runtime)..."
|
|
113
|
+
curl -fsSL https://bun.sh/install | bash
|
|
114
|
+
export BUN_INSTALL="$HOME/.bun"
|
|
115
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
116
|
+
fi
|
|
117
|
+
v=$(bun --version 2>/dev/null)
|
|
118
|
+
v_clean=${v%%-*}
|
|
119
|
+
if ! version_ge "$v_clean" "$MIN_BUN_VERSION"; then
|
|
120
|
+
echo "Bun $MIN_BUN_VERSION+ required (found $v_clean). Upgrade: bun upgrade"
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
bun_bin_dir() { echo "${BUN_INSTALL:-$HOME/.bun}/bin"; }
|
|
126
|
+
|
|
127
|
+
registry_key() {
|
|
128
|
+
if [ -n "$SCOPE" ]; then
|
|
129
|
+
case "$SCOPE" in
|
|
130
|
+
@*) echo "$SCOPE:registry" ;;
|
|
131
|
+
*) echo "@$SCOPE:registry" ;;
|
|
132
|
+
esac
|
|
133
|
+
else
|
|
134
|
+
echo "registry"
|
|
135
|
+
fi
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
validate_registry_url() {
|
|
139
|
+
[ -z "$REGISTRY" ] && return 0
|
|
140
|
+
case "$REGISTRY" in
|
|
141
|
+
http://*|https://*) return 0 ;;
|
|
142
|
+
*) echo "--registry must start with http:// or https:// (got '$REGISTRY')"; exit 1 ;;
|
|
143
|
+
esac
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
require_npm_config() {
|
|
147
|
+
if ! has_npm; then
|
|
148
|
+
echo "npm is required for npm config operations (--persist-registry/--print-registry/--delete-registry)."
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
print_registry() {
|
|
154
|
+
require_npm_config
|
|
155
|
+
key=$(registry_key)
|
|
156
|
+
npm config get "$key"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
delete_registry() {
|
|
160
|
+
require_npm_config
|
|
161
|
+
key=$(registry_key)
|
|
162
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
163
|
+
echo "+ npm config delete $key"
|
|
164
|
+
else
|
|
165
|
+
npm config delete "$key"
|
|
166
|
+
fi
|
|
167
|
+
echo "Deleted npm config key: $key"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
persist_registry() {
|
|
171
|
+
[ "$PERSIST_REGISTRY" = "1" ] || return 0
|
|
172
|
+
validate_registry_url
|
|
173
|
+
require_npm_config
|
|
174
|
+
key=$(registry_key)
|
|
175
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
176
|
+
echo "+ npm config set $key $REGISTRY"
|
|
177
|
+
else
|
|
178
|
+
npm config set "$key" "$REGISTRY"
|
|
179
|
+
fi
|
|
180
|
+
echo "Persisted npm config: $key=$REGISTRY"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
write_project_npmrc() {
|
|
184
|
+
[ "$PROJECT_NPMRC" = "1" ] || return 0
|
|
185
|
+
validate_registry_url
|
|
186
|
+
key=$(registry_key)
|
|
187
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
188
|
+
echo "+ printf '%s=%s\\n' '$key' '$REGISTRY' > .npmrc"
|
|
189
|
+
else
|
|
190
|
+
printf '%s=%s\n' "$key" "$REGISTRY" > .npmrc
|
|
191
|
+
fi
|
|
192
|
+
echo "Wrote project registry config: .npmrc ($key=$REGISTRY)"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
normalize_repo_spec() {
|
|
196
|
+
repo="$1"
|
|
197
|
+
case "$repo" in
|
|
198
|
+
github:*|git+*|ssh://*|git@*) spec="$repo" ;;
|
|
199
|
+
http://*|https://*) spec="git+$repo" ;;
|
|
200
|
+
*/*) spec="github:$repo" ;;
|
|
201
|
+
*) spec="$repo" ;;
|
|
202
|
+
esac
|
|
203
|
+
[ -n "$REF" ] && spec="$spec#$REF"
|
|
204
|
+
echo "$spec"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
run_bun_global_add() {
|
|
208
|
+
spec="$1"
|
|
209
|
+
if [ -n "$REGISTRY" ]; then
|
|
210
|
+
validate_registry_url
|
|
211
|
+
if [ -n "$SCOPE" ] && [ "$PERSIST_REGISTRY" != "1" ] && [ "$PROJECT_NPMRC" != "1" ]; then
|
|
212
|
+
echo "Note: scoped registries need --persist-registry or --project-npmrc for npm-compatible scope config; using $REGISTRY as this install's one-shot registry."
|
|
213
|
+
fi
|
|
214
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
215
|
+
echo "+ NPM_CONFIG_REGISTRY=$REGISTRY npm_config_registry=$REGISTRY bun add -g $spec"
|
|
216
|
+
else
|
|
217
|
+
NPM_CONFIG_REGISTRY="$REGISTRY" npm_config_registry="$REGISTRY" bun add -g "$spec"
|
|
218
|
+
fi
|
|
219
|
+
else
|
|
220
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
221
|
+
echo "+ bun add -g $spec"
|
|
222
|
+
else
|
|
223
|
+
bun add -g "$spec"
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Bun global install (the gjc-idiomatic path). Exposes the package's `joc` bin
|
|
229
|
+
# in Bun's global bin dir (~/.bun/bin/joc).
|
|
230
|
+
install_global() {
|
|
231
|
+
spec=$(normalize_repo_spec "$REPO")
|
|
232
|
+
echo "Installing $PKG globally via Bun ($spec)..."
|
|
233
|
+
run_bun_global_add "$spec"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
install_npm() {
|
|
237
|
+
spec="$PKG"
|
|
238
|
+
[ -n "$REF" ] && spec="$PKG@$REF"
|
|
239
|
+
echo "Installing $PKG globally via Bun ($spec)..."
|
|
240
|
+
run_bun_global_add "$spec"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# Dev install from the current clone: register the package globally with
|
|
244
|
+
# `bun link` so source edits are picked up immediately.
|
|
245
|
+
install_local() {
|
|
246
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
247
|
+
SRC_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
248
|
+
if [ ! -f "$SRC_DIR/src/cli.ts" ]; then
|
|
249
|
+
echo "--local: expected cli.ts at $SRC_DIR/src/cli.ts"
|
|
250
|
+
exit 1
|
|
251
|
+
fi
|
|
252
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
253
|
+
echo "+ ( cd $SRC_DIR && bun install --silent )"
|
|
254
|
+
echo "+ ( cd $SRC_DIR && bun link )"
|
|
255
|
+
else
|
|
256
|
+
( cd "$SRC_DIR" && bun install --silent >/dev/null )
|
|
257
|
+
( cd "$SRC_DIR" && bun link >/dev/null 2>&1 ) || true
|
|
258
|
+
fi
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
install_binary() {
|
|
262
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
263
|
+
SRC_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
264
|
+
if [ ! -f "$SRC_DIR/src/cli.ts" ]; then
|
|
265
|
+
echo "--binary must be run from a clone (expected $SRC_DIR/src/cli.ts)"
|
|
266
|
+
exit 1
|
|
267
|
+
fi
|
|
268
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
269
|
+
echo "+ ( cd $SRC_DIR && bun install --silent )"
|
|
270
|
+
echo "+ bun build src/cli.ts --compile --outfile $INSTALL_DIR/joc"
|
|
271
|
+
else
|
|
272
|
+
( cd "$SRC_DIR" && bun install --silent >/dev/null )
|
|
273
|
+
mkdir -p "$INSTALL_DIR"
|
|
274
|
+
echo "Compiling standalone binary → $INSTALL_DIR/joc ..."
|
|
275
|
+
( cd "$SRC_DIR" && bun build src/cli.ts --compile --outfile "$INSTALL_DIR/joc" >/dev/null )
|
|
276
|
+
chmod +x "$INSTALL_DIR/joc" 2>/dev/null || true
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Add a compatibility symlink in INSTALL_DIR so the documented location keeps
|
|
281
|
+
# working even when Bun's global bin dir is not on PATH.
|
|
282
|
+
link_compat() {
|
|
283
|
+
[ "$DRY_RUN" = "1" ] && return 0
|
|
284
|
+
BUN_BIN="$(bun_bin_dir)"
|
|
285
|
+
[ -e "$BUN_BIN/joc" ] && LINKED="$BUN_BIN/joc"
|
|
286
|
+
mkdir -p "$INSTALL_DIR"
|
|
287
|
+
if [ -n "$LINKED" ]; then
|
|
288
|
+
ln -sf "$LINKED" "$INSTALL_DIR/joc"
|
|
289
|
+
chmod +x "$INSTALL_DIR/joc" 2>/dev/null || true
|
|
290
|
+
fi
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
print_done() {
|
|
294
|
+
BUN_BIN="$(bun_bin_dir)"
|
|
295
|
+
echo ""
|
|
296
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
297
|
+
echo "Dry run complete; no install changes were made."
|
|
298
|
+
return 0
|
|
299
|
+
fi
|
|
300
|
+
[ -n "$LINKED" ] && echo "Linked joc via Bun → $LINKED"
|
|
301
|
+
[ -e "$INSTALL_DIR/joc" ] && echo "Compatibility symlink → $INSTALL_DIR/joc"
|
|
302
|
+
case ":$PATH:" in
|
|
303
|
+
*":$BUN_BIN:"*|*":$INSTALL_DIR:"*) echo "Run: joc --help" ;;
|
|
304
|
+
*) echo "Add $BUN_BIN (or $INSTALL_DIR) to PATH, then run: joc --help" ;;
|
|
305
|
+
esac
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
case "$MODE" in
|
|
309
|
+
registry-print) print_registry; exit 0 ;;
|
|
310
|
+
registry-delete) delete_registry; exit 0 ;;
|
|
311
|
+
esac
|
|
312
|
+
|
|
313
|
+
persist_registry
|
|
314
|
+
write_project_npmrc
|
|
315
|
+
require_bun
|
|
316
|
+
case "$MODE" in
|
|
317
|
+
global) install_global; link_compat ;;
|
|
318
|
+
npm) install_npm; link_compat ;;
|
|
319
|
+
local) install_local; link_compat ;;
|
|
320
|
+
binary) install_binary ;;
|
|
321
|
+
esac
|
|
322
|
+
print_done
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# joc uninstaller — removes binary symlink and optionally global config.
|
|
3
|
+
# Usage:
|
|
4
|
+
# sh scripts/uninstall.sh # remove binary only
|
|
5
|
+
# sh scripts/uninstall.sh --purge # also remove ~/.joc/
|
|
6
|
+
set -e
|
|
7
|
+
INSTALL_DIR="${JOC_INSTALL_DIR:-$HOME/.local/bin}"
|
|
8
|
+
PURGE=0
|
|
9
|
+
[ "$1" = "--purge" ] && PURGE=1
|
|
10
|
+
|
|
11
|
+
if [ -L "$INSTALL_DIR/joc" ] || [ -f "$INSTALL_DIR/joc" ]; then
|
|
12
|
+
rm -f "$INSTALL_DIR/joc"
|
|
13
|
+
echo "Removed $INSTALL_DIR/joc"
|
|
14
|
+
else
|
|
15
|
+
echo "No joc binary at $INSTALL_DIR/joc"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Remove the bun-native link (bin + global registry entry).
|
|
19
|
+
BUN_BIN="${BUN_INSTALL:-$HOME/.bun}/bin"
|
|
20
|
+
if [ -L "$BUN_BIN/joc" ] || [ -f "$BUN_BIN/joc" ]; then
|
|
21
|
+
rm -f "$BUN_BIN/joc"
|
|
22
|
+
echo "Removed $BUN_BIN/joc (bun link)"
|
|
23
|
+
fi
|
|
24
|
+
GLOBAL_PKG="${BUN_INSTALL:-$HOME/.bun}/install/global/node_modules/jeo-code"
|
|
25
|
+
[ -e "$GLOBAL_PKG" ] && rm -rf "$GLOBAL_PKG" && echo "Unregistered jeo-code from bun global"
|
|
26
|
+
|
|
27
|
+
if [ "$PURGE" = "1" ]; then
|
|
28
|
+
rm -rf "$HOME/.joc"
|
|
29
|
+
echo "Removed ~/.joc/"
|
|
30
|
+
fi
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { callLlm, type Message } from "./loop";
|
|
2
|
+
|
|
3
|
+
export interface CompactionOptions {
|
|
4
|
+
maxMessages?: number;
|
|
5
|
+
keepRecent?: number;
|
|
6
|
+
model?: string;
|
|
7
|
+
/** User-initiated `/compact`: lower the trigger floor so it actually compacts a small history. */
|
|
8
|
+
force?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CompactionResult {
|
|
12
|
+
compacted: boolean;
|
|
13
|
+
removed: number;
|
|
14
|
+
summary?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function maybeCompact(
|
|
18
|
+
history: Message[],
|
|
19
|
+
opts: CompactionOptions = {}
|
|
20
|
+
): Promise<CompactionResult> {
|
|
21
|
+
const maxMessages = opts.maxMessages ?? (opts.force ? 1 : 40);
|
|
22
|
+
const keepRecent = opts.keepRecent ?? (opts.force ? 4 : 12);
|
|
23
|
+
|
|
24
|
+
const hasSystem = history.length > 0 && history[0].role === "system";
|
|
25
|
+
const systemCount = hasSystem ? 1 : 0;
|
|
26
|
+
const body = history.slice(systemCount);
|
|
27
|
+
|
|
28
|
+
if (body.length <= maxMessages) {
|
|
29
|
+
return { compacted: false, removed: 0 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const olderCount = body.length - keepRecent;
|
|
33
|
+
if (olderCount <= 0) {
|
|
34
|
+
return { compacted: false, removed: 0 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const older = body.slice(0, olderCount);
|
|
38
|
+
const recent = body.slice(olderCount);
|
|
39
|
+
|
|
40
|
+
const olderFormatted = older
|
|
41
|
+
.map(msg => `[${msg.role}] ${msg.content}`)
|
|
42
|
+
.join("\n");
|
|
43
|
+
|
|
44
|
+
const systemPrompt =
|
|
45
|
+
"Summarize the following coding-agent conversation so work can continue. Capture decisions, files changed, current task state, and open TODOs. Be concise.";
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const summary = await callLlm(
|
|
49
|
+
[
|
|
50
|
+
{ role: "user", content: olderFormatted }
|
|
51
|
+
],
|
|
52
|
+
{
|
|
53
|
+
model: opts.model,
|
|
54
|
+
systemPrompt,
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const systemMessages = hasSystem ? [history[0]] : [];
|
|
59
|
+
const next: Message[] = [
|
|
60
|
+
...systemMessages,
|
|
61
|
+
{ role: "user", content: "[Earlier conversation summary]\n" + summary },
|
|
62
|
+
...recent
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
history.splice(0, history.length, ...next);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
compacted: true,
|
|
69
|
+
removed: older.length,
|
|
70
|
+
summary,
|
|
71
|
+
};
|
|
72
|
+
} catch {
|
|
73
|
+
return { compacted: false, removed: 0 };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runtime validation for `~/.joc/config.json`. Previously the file was
|
|
5
|
+
* `JSON.parse`d and cast straight to `Config` — a wrong-typed field (e.g. a
|
|
6
|
+
* numeric `defaultModel`) slipped through untyped and surfaced as a confusing
|
|
7
|
+
* downstream failure. `parseConfig` turns that into a clear, actionable signal.
|
|
8
|
+
*/
|
|
9
|
+
const StoredOAuthSchema = z.object({
|
|
10
|
+
access: z.string(),
|
|
11
|
+
refresh: z.string().optional(),
|
|
12
|
+
expires: z.number().optional(),
|
|
13
|
+
accountId: z.string().optional(),
|
|
14
|
+
email: z.string().optional(),
|
|
15
|
+
projectId: z.string().optional(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const OAuthEntry = z.union([z.string(), StoredOAuthSchema]);
|
|
19
|
+
|
|
20
|
+
export const ConfigSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
providers: z
|
|
23
|
+
.object({
|
|
24
|
+
anthropic: z.string().optional(),
|
|
25
|
+
openai: z.string().optional(),
|
|
26
|
+
gemini: z.string().optional(),
|
|
27
|
+
})
|
|
28
|
+
.default({}),
|
|
29
|
+
oauth: z
|
|
30
|
+
.object({
|
|
31
|
+
anthropic: OAuthEntry.optional(),
|
|
32
|
+
openai: OAuthEntry.optional(),
|
|
33
|
+
gemini: OAuthEntry.optional(),
|
|
34
|
+
})
|
|
35
|
+
.optional(),
|
|
36
|
+
ollamaBaseUrl: z.string().optional(),
|
|
37
|
+
openaiBaseUrl: z.string().optional(),
|
|
38
|
+
defaultModel: z.string().min(1),
|
|
39
|
+
thinkingLevel: z.enum(["minimal", "low", "medium", "high", "xhigh"]).optional(),
|
|
40
|
+
modelAliases: z.record(z.string()).optional(),
|
|
41
|
+
/**
|
|
42
|
+
* Provider retry budgets (gjc parity). `requestMaxRetries` counts retries
|
|
43
|
+
* (not the initial request) for a provider request; `maxDelayMs` caps backoff.
|
|
44
|
+
* `maxRetries`/`streamMaxRetries` are accepted for gjc-config compatibility.
|
|
45
|
+
*/
|
|
46
|
+
retry: z
|
|
47
|
+
.object({
|
|
48
|
+
requestMaxRetries: z.number().int().min(0).optional(),
|
|
49
|
+
streamMaxRetries: z.number().int().min(0).optional(),
|
|
50
|
+
maxRetries: z.number().int().min(0).optional(),
|
|
51
|
+
maxDelayMs: z.number().int().min(0).optional(),
|
|
52
|
+
})
|
|
53
|
+
.optional(),
|
|
54
|
+
/**
|
|
55
|
+
* Per-subagent-role overrides (gjc role-agent parity). Keyed by role id
|
|
56
|
+
* (executor / planner / architect / critic); each may pin a model and/or a
|
|
57
|
+
* step budget. Tolerant of unknown keys.
|
|
58
|
+
*/
|
|
59
|
+
subagents: z
|
|
60
|
+
.record(
|
|
61
|
+
z.object({
|
|
62
|
+
model: z.string().optional(),
|
|
63
|
+
maxSteps: z.number().int().min(1).optional(),
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
.optional(),
|
|
67
|
+
/** Model role tiers (smol/slow/plan); each falls back to defaultModel. */
|
|
68
|
+
roles: z
|
|
69
|
+
.object({
|
|
70
|
+
smol: z.string().optional(),
|
|
71
|
+
slow: z.string().optional(),
|
|
72
|
+
plan: z.string().optional(),
|
|
73
|
+
})
|
|
74
|
+
.optional(),
|
|
75
|
+
})
|
|
76
|
+
.passthrough();
|
|
77
|
+
|
|
78
|
+
export type ValidatedConfig = z.infer<typeof ConfigSchema>;
|
|
79
|
+
|
|
80
|
+
/** Validate parsed JSON against the config schema. Returns a tagged result, never throws. */
|
|
81
|
+
export function parseConfig(raw: unknown): { ok: true; config: ValidatedConfig } | { ok: false; message: string } {
|
|
82
|
+
const result = ConfigSchema.safeParse(raw);
|
|
83
|
+
if (result.success) return { ok: true, config: result.data };
|
|
84
|
+
const issue = result.error.issues[0];
|
|
85
|
+
const where = issue?.path?.length ? issue.path.join(".") : "config";
|
|
86
|
+
return { ok: false, message: `${where}: ${issue?.message ?? "invalid"}` };
|
|
87
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ProjectContextFile {
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const CONTEXT_CANDIDATES = ["JEO.md", "AGENTS.md", ".joc/context.md", "CLAUDE.md"];
|
|
10
|
+
|
|
11
|
+
export async function loadProjectContext(cwd = process.cwd()): Promise<ProjectContextFile[]> {
|
|
12
|
+
const result: ProjectContextFile[] = [];
|
|
13
|
+
|
|
14
|
+
for (const candidate of CONTEXT_CANDIDATES) {
|
|
15
|
+
const filePath = path.join(cwd, candidate);
|
|
16
|
+
try {
|
|
17
|
+
const stat = await fs.stat(filePath);
|
|
18
|
+
if (stat.isFile() && stat.size > 0) {
|
|
19
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
20
|
+
if (content.length > 0) {
|
|
21
|
+
let finalContent = content;
|
|
22
|
+
if (content.length > 16000) {
|
|
23
|
+
finalContent = content.slice(0, 16000) + "\n…(truncated)";
|
|
24
|
+
}
|
|
25
|
+
result.push({
|
|
26
|
+
path: candidate,
|
|
27
|
+
content: finalContent,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// Skip missing, unreadable, or directories/empty files
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function withProjectContext(systemPrompt: string, contextFiles: ProjectContextFile[]): string {
|
|
40
|
+
if (!contextFiles || contextFiles.length === 0) {
|
|
41
|
+
return systemPrompt;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let contextBlock = "<project_context>\n\nProject-specific instructions and guidelines:\n";
|
|
45
|
+
for (const file of contextFiles) {
|
|
46
|
+
contextBlock += `\n<project_instructions path="${file.path}">\n${file.content}\n</project_instructions>\n`;
|
|
47
|
+
}
|
|
48
|
+
contextBlock += "</project_context>";
|
|
49
|
+
|
|
50
|
+
return `${systemPrompt}\n\n${contextBlock}`;
|
|
51
|
+
}
|