@jjlabsio/claude-crew 0.1.46 → 0.1.48

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.
@@ -11,7 +11,7 @@
11
11
  "name": "claude-crew",
12
12
  "source": "./",
13
13
  "description": "오케스트레이터 + PM, 플래너, 개발, QA, 마케팅 에이전트 팀으로 단일 제품의 개발과 마케팅을 통합 관리",
14
- "version": "0.1.46",
14
+ "version": "0.1.48",
15
15
  "author": {
16
16
  "name": "Jaejin Song",
17
17
  "email": "wowlxx28@gmail.com"
@@ -28,5 +28,5 @@
28
28
  "category": "workflow"
29
29
  }
30
30
  ],
31
- "version": "0.1.46"
31
+ "version": "0.1.48"
32
32
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-crew",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
5
5
  "author": {
6
6
  "name": "Jaejin Song",
@@ -44,7 +44,7 @@
44
44
  "explorer": { "codex_sandbox": "read-only" },
45
45
  "researcher": { "codex_sandbox": "read-only" },
46
46
  "qa": { "codex_sandbox": "read-only" },
47
- "dev": { "codex_sandbox": "workspace-write" },
47
+ "dev": { "codex_sandbox": "workspace-write", "codex_network_access": true },
48
48
  "code-reviewer": { "codex_sandbox": "read-only" }
49
49
  }
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlabsio/claude-crew",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
5
5
  "author": "Jaejin Song <wowlxx28@gmail.com>",
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@ function cleanCodexStderr(stderr) {
56
56
 
57
57
  /** @returns {ThreadStartParams} */
58
58
  function buildThreadParams(cwd, options = {}) {
59
- return {
59
+ const params = {
60
60
  cwd,
61
61
  model: options.model ?? null,
62
62
  approvalPolicy: options.approvalPolicy ?? "never",
@@ -65,17 +65,25 @@ function buildThreadParams(cwd, options = {}) {
65
65
  ephemeral: options.ephemeral ?? true,
66
66
  experimentalRawEvents: false
67
67
  };
68
+ if (options.networkAccess) {
69
+ params.networkAccess = true;
70
+ }
71
+ return params;
68
72
  }
69
73
 
70
74
  /** @returns {ThreadResumeParams} */
71
75
  function buildResumeParams(threadId, cwd, options = {}) {
72
- return {
76
+ const params = {
73
77
  threadId,
74
78
  cwd,
75
79
  model: options.model ?? null,
76
80
  approvalPolicy: options.approvalPolicy ?? "never",
77
81
  sandbox: options.sandbox ?? "read-only"
78
82
  };
83
+ if (options.networkAccess) {
84
+ params.networkAccess = true;
85
+ }
86
+ return params;
79
87
  }
80
88
 
81
89
  /** @returns {UserInput[]} */
@@ -977,6 +985,7 @@ export async function runAppServerTurn(cwd, options = {}) {
977
985
  const response = await resumeThread(client, options.resumeThreadId, cwd, {
978
986
  model: options.model,
979
987
  sandbox: options.sandbox,
988
+ networkAccess: options.networkAccess,
980
989
  ephemeral: false
981
990
  });
982
991
  threadId = response.thread.id;
@@ -985,6 +994,7 @@ export async function runAppServerTurn(cwd, options = {}) {
985
994
  const response = await startThread(client, cwd, {
986
995
  model: options.model,
987
996
  sandbox: options.sandbox,
997
+ networkAccess: options.networkAccess,
988
998
  ephemeral: options.persistThread ? false : true,
989
999
  threadName: options.persistThread ? options.threadName : options.threadName ?? null
990
1000
  });
@@ -73,7 +73,7 @@ function printUsage() {
73
73
  console.log(
74
74
  [
75
75
  "Usage:",
76
- " node scripts/crew-codex-companion.mjs task [--background] [--write] [--expect-crew-result] [--resume-last|--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [prompt]",
76
+ " node scripts/crew-codex-companion.mjs task [--background] [--write] [--network-access] [--expect-crew-result] [--resume-last|--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [prompt]",
77
77
  " node scripts/crew-codex-companion.mjs status [job-id] [--all] [--json]",
78
78
  " node scripts/crew-codex-companion.mjs result [job-id] [--json]",
79
79
  " node scripts/crew-codex-companion.mjs cancel [job-id] [--json]"
@@ -341,6 +341,7 @@ async function executeTaskRun(request) {
341
341
  model: request.model,
342
342
  effort: request.effort,
343
343
  sandbox: request.write ? "workspace-write" : "read-only",
344
+ networkAccess: Boolean(request.networkAccess),
344
345
  onProgress: request.onProgress,
345
346
  persistThread: true,
346
347
  threadName: resumeThreadId ? null : buildPersistentTaskThreadName(request.prompt || DEFAULT_CONTINUE_PROMPT)
@@ -455,13 +456,14 @@ function buildTaskJob(workspaceRoot, taskMetadata, write) {
455
456
  });
456
457
  }
457
458
 
458
- function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, expectCrewResult, jobId }) {
459
+ function buildTaskRequest({ cwd, model, effort, prompt, write, networkAccess, resumeLast, expectCrewResult, jobId }) {
459
460
  return {
460
461
  cwd,
461
462
  model,
462
463
  effort,
463
464
  prompt,
464
465
  write,
466
+ networkAccess,
465
467
  resumeLast,
466
468
  expectCrewResult,
467
469
  jobId
@@ -591,7 +593,7 @@ function enqueueBackgroundTask(cwd, job, request) {
591
593
  async function handleTask(argv) {
592
594
  const { options, positionals } = parseCommandInput(argv, {
593
595
  valueOptions: ["model", "effort", "cwd", "prompt-file"],
594
- booleanOptions: ["json", "write", "expect-crew-result", "resume-last", "resume", "fresh", "background"],
596
+ booleanOptions: ["json", "write", "network-access", "expect-crew-result", "resume-last", "resume", "fresh", "background"],
595
597
  aliasMap: {
596
598
  m: "model"
597
599
  }
@@ -609,6 +611,7 @@ async function handleTask(argv) {
609
611
  throw new Error("Choose either --resume/--resume-last or --fresh.");
610
612
  }
611
613
  const write = Boolean(options.write);
614
+ const networkAccess = Boolean(options["network-access"]);
612
615
  const expectCrewResult = Boolean(options["expect-crew-result"]);
613
616
  const taskMetadata = buildTaskRunMetadata({
614
617
  prompt,
@@ -626,6 +629,7 @@ async function handleTask(argv) {
626
629
  effort,
627
630
  prompt,
628
631
  write,
632
+ networkAccess,
629
633
  resumeLast,
630
634
  expectCrewResult,
631
635
  jobId: job.id
@@ -645,6 +649,7 @@ async function handleTask(argv) {
645
649
  effort,
646
650
  prompt,
647
651
  write,
652
+ networkAccess,
648
653
  resumeLast,
649
654
  expectCrewResult,
650
655
  jobId: job.id,
@@ -264,6 +264,10 @@ function buildTaskArgs(input, promptFile) {
264
264
  args.push("--write");
265
265
  }
266
266
 
267
+ if (input.resolved?.codex_network_access) {
268
+ args.push("--network-access");
269
+ }
270
+
267
271
  if (input.resolved?.model) {
268
272
  args.push("--model", input.resolved.model);
269
273
  }
@@ -23,6 +23,7 @@ export function resolveRole(input) {
23
23
  if (!["read-only", "workspace-write"].includes(codexSandbox)) {
24
24
  throw new Error(`Missing codex_sandbox for role ${role}`);
25
25
  }
26
+ const codexNetworkAccess = catalog?.agent_runtime?.[role]?.codex_network_access === true;
26
27
 
27
28
  const warnings = [];
28
29
  const workspaceAccess = contract?.capabilities?.workspaceAccess;
@@ -38,6 +39,7 @@ export function resolveRole(input) {
38
39
  model,
39
40
  reasoning: provider === "codex" ? defaults.reasoning ?? null : null,
40
41
  codex_sandbox: codexSandbox,
42
+ codex_network_access: codexNetworkAccess,
41
43
  contract,
42
44
  dispatch: {
43
45
  path: provider === "codex" ? "codex" : "claude",
@@ -11,6 +11,7 @@ import { execSync } from 'node:child_process';
11
11
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
12
12
  import { join, dirname } from 'node:path';
13
13
  import { homedir } from 'node:os';
14
+ import { PLUGIN_ROOT } from './lib/pluginRoot.mjs';
14
15
 
15
16
  // ---------------------------------------------------------------------------
16
17
  // Read stdin (with timeout)
@@ -33,6 +34,39 @@ function gitExec(cmd, cwd) {
33
34
  } catch { return null; }
34
35
  }
35
36
 
37
+ function readPluginVersion() {
38
+ const packageJson = JSON.parse(readFileSync(join(PLUGIN_ROOT, 'package.json'), 'utf-8'));
39
+ return packageJson.version;
40
+ }
41
+
42
+ function updatePluginRootCache(pluginRoot) {
43
+ try {
44
+ const crewDir = join(homedir(), '.claude', 'crew');
45
+ const cachePath = join(crewDir, 'plugin-root.json');
46
+
47
+ let shouldWrite = !existsSync(cachePath);
48
+ if (!shouldWrite) {
49
+ try {
50
+ const current = JSON.parse(readFileSync(cachePath, 'utf-8'));
51
+ shouldWrite = current.pluginRoot !== pluginRoot;
52
+ } catch {
53
+ shouldWrite = true;
54
+ }
55
+ }
56
+
57
+ if (!shouldWrite) return;
58
+
59
+ mkdirSync(crewDir, { recursive: true });
60
+ writeFileSync(cachePath, JSON.stringify({
61
+ pluginRoot,
62
+ version: readPluginVersion(),
63
+ updatedAt: new Date().toISOString(),
64
+ }, null, 2));
65
+ } catch {
66
+ // Best-effort cache for runner fallback. HUD setup must continue on failure.
67
+ }
68
+ }
69
+
36
70
  // ---------------------------------------------------------------------------
37
71
  // Main
38
72
  // ---------------------------------------------------------------------------
@@ -74,6 +108,8 @@ async function main() {
74
108
  writeFileSync(localSettingsPath, JSON.stringify(localSettings, null, 2));
75
109
  }
76
110
 
111
+ updatePluginRootCache(pluginRoot);
112
+
77
113
  console.log(JSON.stringify({ continue: true }));
78
114
  } catch (e) {
79
115
  console.log(JSON.stringify({
@@ -7,20 +7,33 @@ description: 모든 crew 에이전트 dispatch의 중앙 규약 — provider별
7
7
 
8
8
  crew 업무 스킬은 에이전트 provider별 호출 세부사항을 직접 구현하지 않고 이 중앙 규약을 따른다. 본 스킬은 prepare, resolve, dispatch, resume, followup 주입, retry/fallback/escalate 판단의 공통 표면을 정의한다.
9
9
 
10
- 설치 후 drift 차단용 pre-commit hook은 `node scripts/crew-agent-runner.mjs install-hooks`로 설치한다.
10
+ 설치 후 drift 차단용 pre-commit hook은 `node "$CREW_ROOT/scripts/crew-agent-runner.mjs" install-hooks`로 설치한다.
11
11
  (plugin 개발자 전용 — 사용자는 호출하지 않습니다. build/validate는 plugin source repo의 drift 차단 도구입니다.)
12
12
 
13
13
  ## Dispatch 절차
14
14
 
15
15
  업무 스킬(crew-plan/crew-interview/crew-dev)이 role을 실행해야 할 때 본 절차를 따른다.
16
16
 
17
+ ### Step 0. Runner 경로 결정
18
+
19
+ 오케스트레이터는 runner 호출 전에 plugin root를 결정하고, 결정된 경로를 `CREW_ROOT`로 사용한다.
20
+
21
+ Fallback 체인 (순서 중요):
22
+
23
+ 1. `$CLAUDE_PLUGIN_ROOT`가 있으면 사용한다. 정상 Claude Code plugin 환경이므로 추가 검증하지 않는다.
24
+ 2. `~/.claude/crew/plugin-root.json`의 `pluginRoot`를 읽는다. 해당 경로에 `scripts/crew-agent-runner.mjs`가 존재하면 사용하고, 없으면 다음 fallback으로 진행한다.
25
+ 3. `git rev-parse --show-toplevel` 결과를 dev 환경 전용 후보로 사용한다. 해당 경로의 `package.json`에서 `name`이 `@jjlabsio/claude-crew`인지 검증하고, 맞으면 사용한다. 아니면 다음 fallback으로 진행한다.
26
+ 4. 모든 fallback이 실패하면 `'/crew-setup을 먼저 실행하세요'` 에러 메시지로 중단한다.
27
+
28
+ 이후 모든 runner 호출은 `node "$CREW_ROOT/scripts/crew-agent-runner.mjs"` 형식을 사용한다.
29
+
17
30
  ### 1. request 객체 작성
18
31
 
19
32
  `{ role, inputs (path+content), instruction, successGate, failureHandling, taskId }` 형태의 임시 JSON 파일을 작성한다.
20
33
 
21
34
  ### 2. prepare
22
35
 
23
- 오케스트레이터는 `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
36
+ 오케스트레이터는 `node "$CREW_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
24
37
  prepare는 provider/model/contract를 해석하고 다음 action 중 하나를 반환한다.
25
38
 
26
39
  ### 3a. dispatch action
@@ -67,8 +80,8 @@ capability를 넘어선 도구 요청이다. 오케스트레이터가 `contract.
67
80
 
68
81
  ### Codex 경로
69
82
 
70
- 1. `node scripts/crew-agent-runner.mjs render-followup --previous-result <file> --new-input <file>` 실행 → followup prompt 문자열 → 임시 파일에 저장.
71
- 2. `node scripts/crew-agent-runner.mjs dispatch --role <role> --request-file <new-request-with-followup-prompt> --resume-handle <agent_handle> --json` 실행.
83
+ 1. `node "$CREW_ROOT/scripts/crew-agent-runner.mjs" render-followup --previous-result <file> --new-input <file>` 실행 → followup prompt 문자열 → 임시 파일에 저장.
84
+ 2. `node "$CREW_ROOT/scripts/crew-agent-runner.mjs" dispatch --role <role> --request-file <new-request-with-followup-prompt> --resume-handle <agent_handle> --json` 실행.
72
85
  - 내부적으로 runner가 `crew-codex-companion.mjs task-resume-candidate`로 thread 일치 검증 후 `task --resume-last`를 호출하고 AgentResult를 정규화한다.
73
86
  3. AgentResult JSON을 받아 다음 상태 처리.
74
87
 
@@ -102,6 +102,27 @@ push는 하지 않는다.
102
102
 
103
103
  ---
104
104
 
105
+ ## Step 2b — Plugin Root 영속화
106
+
107
+ `$CLAUDE_PLUGIN_ROOT`가 살아있는 setup 시점에 runner fallback용 plugin root를 사용자 데이터 영역에 저장한다.
108
+
109
+ 1. `~/.claude/crew/` 디렉토리가 없으면 생성한다.
110
+ 2. `~/.claude/crew/plugin-root.json`을 다음 형식으로 쓴다. 기존 파일이 있으면 덮어쓴다.
111
+ ```json
112
+ {
113
+ "pluginRoot": "<$CLAUDE_PLUGIN_ROOT 값>",
114
+ "version": "<현재 플러그인 버전>",
115
+ "updatedAt": "<ISO timestamp>"
116
+ }
117
+ ```
118
+ 3. plugin 자체 데이터인 version은 plugin root 기준 `package.json`에서 읽는다.
119
+
120
+ **주의**:
121
+ - plugin root는 `$CLAUDE_PLUGIN_ROOT` 값을 사용한다.
122
+ - `~/.claude/crew/plugin-root.json`은 사용자 데이터이므로 home 기준 경로에 저장한다.
123
+
124
+ ---
125
+
105
126
  ## Step 3 — Provider 설정
106
127
 
107
128
  에이전트별로 어떤 provider(claude/codex)와 model을 사용할지 설정한다.