teamcopilot 0.3.1 → 0.3.3

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.
Files changed (22) hide show
  1. package/dist/frontend/assets/{cssMode-DVODTskC.js → cssMode-DXQJFMVx.js} +1 -1
  2. package/dist/frontend/assets/{freemarker2-VNR3qIlX.js → freemarker2-BW1EZI_f.js} +1 -1
  3. package/dist/frontend/assets/{handlebars-Tl9rb_wi.js → handlebars-Biheo7KR.js} +1 -1
  4. package/dist/frontend/assets/{html-DPF40zp3.js → html-BH01Jifq.js} +1 -1
  5. package/dist/frontend/assets/{htmlMode-zWkU3Y3t.js → htmlMode-BhB6cjl6.js} +1 -1
  6. package/dist/frontend/assets/index-CaegH8GM.css +1 -0
  7. package/dist/frontend/assets/{index-BBQJYgr0.js → index-Dl290_nP.js} +3 -3
  8. package/dist/frontend/assets/{javascript-BpsgEO5T.js → javascript-vRQ7Z-SD.js} +1 -1
  9. package/dist/frontend/assets/{jsonMode-CczDM46r.js → jsonMode-CQXvACVS.js} +1 -1
  10. package/dist/frontend/assets/{liquid-LFSrT6MG.js → liquid-H0P3dWK2.js} +1 -1
  11. package/dist/frontend/assets/{mdx-CIgkEV38.js → mdx-Bz0q5Fh9.js} +1 -1
  12. package/dist/frontend/assets/{python-f-APDdLJ.js → python-v4fpwY3A.js} +1 -1
  13. package/dist/frontend/assets/{razor-CcZ5caQe.js → razor-ptFSsABU.js} +1 -1
  14. package/dist/frontend/assets/{tsMode-BeWp_Zmp.js → tsMode-DgGhJJ5C.js} +1 -1
  15. package/dist/frontend/assets/{typescript-BWSPzIAG.js → typescript-C7Kjd36G.js} +1 -1
  16. package/dist/frontend/assets/{xml-kQvpQHq-.js → xml-BBvPtCBs.js} +1 -1
  17. package/dist/frontend/assets/{yaml-Bl9AEojJ.js → yaml-dCpIE0um.js} +1 -1
  18. package/dist/frontend/index.html +3 -2
  19. package/dist/workspace_files/.opencode/plugins/secret-proxy.ts +119 -7
  20. package/dist/workspace_files/AGENTS.md +4 -3
  21. package/package.json +1 -1
  22. package/dist/frontend/assets/index-CQhgzoZH.css +0 -1
@@ -324,6 +324,12 @@ function isCurlExecutableToken(rawToken: string): boolean {
324
324
  return base === "curl"
325
325
  }
326
326
 
327
+ function isGitExecutableToken(rawToken: string): boolean {
328
+ const { inner } = unwrapToken(rawToken)
329
+ const base = inner.split("/").pop() ?? inner
330
+ return base === "git"
331
+ }
332
+
327
333
  function getLongOptionName(inner: string): string | null {
328
334
  const eqIndex = inner.indexOf("=")
329
335
  const optionName = eqIndex === -1 ? inner : inner.slice(0, eqIndex)
@@ -546,7 +552,12 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
546
552
  return cached
547
553
  }
548
554
 
549
- const rewritten = substitutePlaceholdersInCurlShellString(text)
555
+ const curlRewritten = substitutePlaceholdersInCurlShellString(text)
556
+ const gitRewritten = substitutePlaceholdersInGitShellString(curlRewritten.rewritten)
557
+ const rewritten = {
558
+ rewritten: gitRewritten.rewritten,
559
+ referencedKeys: Array.from(new Set([...curlRewritten.referencedKeys, ...gitRewritten.referencedKeys])).sort(),
560
+ }
550
561
  cache.set(text, rewritten)
551
562
  return rewritten
552
563
  }
@@ -564,6 +575,27 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
564
575
  }
565
576
  }
566
577
 
578
+ function rewriteGitRawToken(rawToken: string): { rewritten: string; referencedKeys: string[] } {
579
+ const { quote, inner } = unwrapToken(rawToken)
580
+ const { rewritten, referencedKeys } = rewriteTokenInner(inner)
581
+ if (referencedKeys.length === 0) {
582
+ return {
583
+ rewritten: rawToken,
584
+ referencedKeys,
585
+ }
586
+ }
587
+ if (quote === null && rawToken.includes("'")) {
588
+ return {
589
+ rewritten: `"${escapeForDoubleQuotedShell(rewritten.replace(/'/g, ""))}"`,
590
+ referencedKeys,
591
+ }
592
+ }
593
+ return {
594
+ rewritten: wrapTokenForShellExpansion(quote, rewritten, true),
595
+ referencedKeys,
596
+ }
597
+ }
598
+
567
599
  function rewriteRawTokenValuePortion(rawToken: string): { rewritten: string; referencedKeys: string[] } {
568
600
  const { quote, inner } = unwrapToken(rawToken)
569
601
  const eqIndex = inner.indexOf("=")
@@ -760,6 +792,82 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
760
792
  }
761
793
  }
762
794
 
795
+ function substitutePlaceholdersInGitShellString(
796
+ text: string,
797
+ ): { rewritten: string; referencedKeys: string[] } {
798
+ const rawTokens = tokenizeCommand(text)
799
+ if (rawTokens.length === 0) {
800
+ return {
801
+ rewritten: text,
802
+ referencedKeys: [],
803
+ }
804
+ }
805
+
806
+ const replacements: Array<{ start: number; end: number; text: string }> = []
807
+ const referencedKeys = new Set<string>()
808
+ let mutated = false
809
+ let atCommandStart = true
810
+
811
+ for (let index = 0; index < rawTokens.length; index += 1) {
812
+ const rawToken = rawTokens[index]
813
+
814
+ if (SHELL_CONTROL_TOKENS.has(rawToken.raw)) {
815
+ atCommandStart = true
816
+ continue
817
+ }
818
+
819
+ if (!atCommandStart) {
820
+ continue
821
+ }
822
+
823
+ if (!isGitExecutableToken(rawToken.raw)) {
824
+ atCommandStart = false
825
+ continue
826
+ }
827
+
828
+ atCommandStart = false
829
+ for (let j = index + 1; j < rawTokens.length; j += 1) {
830
+ const segmentToken = rawTokens[j]
831
+ if (SHELL_CONTROL_TOKENS.has(segmentToken.raw)) {
832
+ atCommandStart = true
833
+ index = j - 1
834
+ break
835
+ }
836
+
837
+ const rewritten = rewriteGitRawToken(segmentToken.raw)
838
+ if (rewritten.rewritten !== segmentToken.raw) {
839
+ replacements.push({
840
+ start: segmentToken.start,
841
+ end: segmentToken.end,
842
+ text: rewritten.rewritten,
843
+ })
844
+ mutated = true
845
+ }
846
+ for (const key of rewritten.referencedKeys) {
847
+ referencedKeys.add(key)
848
+ }
849
+ }
850
+ }
851
+
852
+ let rewrittenText = text
853
+ if (mutated) {
854
+ const output: string[] = []
855
+ let cursor = 0
856
+ for (const replacement of replacements.sort((left, right) => left.start - right.start)) {
857
+ output.push(text.slice(cursor, replacement.start))
858
+ output.push(replacement.text)
859
+ cursor = replacement.end
860
+ }
861
+ output.push(text.slice(cursor))
862
+ rewrittenText = output.join("")
863
+ }
864
+
865
+ return {
866
+ rewritten: rewrittenText,
867
+ referencedKeys: Array.from(referencedKeys).sort(),
868
+ }
869
+ }
870
+
763
871
  return {
764
872
  "command.execute.before": async (input) => {
765
873
  const sessionID = typeof input.sessionID === "string" ? input.sessionID.trim() : ""
@@ -772,11 +880,13 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
772
880
 
773
881
  const commandCache = new Map<string, { rewritten: string; referencedKeys: string[] }>()
774
882
  if (typeof input.command === "string" && input.command.includes("{{SECRET:")) {
775
- if (isCurlExecutableToken(input.command) && typeof input.arguments === "string") {
776
- const fullCurlCommand = input.arguments.trim().length > 0
883
+ if ((isCurlExecutableToken(input.command) || isGitExecutableToken(input.command)) && typeof input.arguments === "string") {
884
+ const fullCommand = input.arguments.trim().length > 0
777
885
  ? `${input.command} ${input.arguments}`
778
886
  : input.command
779
- const rewritten = substitutePlaceholdersInCurlShellString(fullCurlCommand)
887
+ const rewritten = isCurlExecutableToken(input.command)
888
+ ? substitutePlaceholdersInCurlShellString(fullCommand)
889
+ : substitutePlaceholdersInGitShellString(fullCommand)
780
890
  const prefix = `${input.command} `
781
891
  if (rewritten.rewritten.startsWith(prefix)) {
782
892
  input.arguments = rewritten.rewritten.slice(prefix.length)
@@ -790,11 +900,13 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
790
900
  }
791
901
  }
792
902
  if (typeof input.arguments === "string" && input.arguments.includes("{{SECRET:")) {
793
- if (isCurlExecutableToken(input.command)) {
794
- const fullCurlCommand = input.arguments.trim().length > 0
903
+ if (isCurlExecutableToken(input.command) || isGitExecutableToken(input.command)) {
904
+ const fullCommand = input.arguments.trim().length > 0
795
905
  ? `${input.command} ${input.arguments}`
796
906
  : input.command
797
- const rewritten = substitutePlaceholdersInCurlShellString(fullCurlCommand)
907
+ const rewritten = isCurlExecutableToken(input.command)
908
+ ? substitutePlaceholdersInCurlShellString(fullCommand)
909
+ : substitutePlaceholdersInGitShellString(fullCommand)
798
910
  const prefix = `${input.command} `
799
911
  if (rewritten.rewritten.startsWith(prefix)) {
800
912
  input.arguments = rewritten.rewritten.slice(prefix.length)
@@ -434,17 +434,18 @@ required_secrets:
434
434
  ```
435
435
  - Skill bodies may contain placeholders like `{{SECRET:OPENAI_API_KEY}}`.
436
436
  - When using secrets in bash commands, use placeholder references like `{{SECRET:OPENAI_API_KEY}}` (general form is `{{SECRET:KEY_NAME}}`).
437
- - In bash, TeamCopilot only substitutes `{{SECRET:KEY}}` placeholders in supported `curl` use cases. Unsupported contexts are left unchanged.
437
+ - In bash, TeamCopilot substitutes `{{SECRET:KEY}}` placeholders in supported top-level `curl` use cases and top-level `git` commands. Unsupported contexts are left unchanged.
438
438
  - TeamCopilot may internally rewrite supported secret placeholders to runtime-only env references such as `${__TEAMCOPILOT_RUNTIME_SECRET_OPENAI_API_KEY}` before execution. Those `__TEAMCOPILOT_RUNTIME_SECRET_*` references are internal only.
439
439
  - Never write `__TEAMCOPILOT_RUNTIME_SECRET_*` yourself in tool calls, scripts, or command text, even without `$` or `${}`. Agent-authored `__TEAMCOPILOT_RUNTIME_SECRET_*` references are rejected. Always use `{{SECRET:KEY}}` instead.
440
440
  - Supported `curl` substitution contexts include:
441
441
  - auth-like headers passed with `-H` / `--header` when the header name is an allowed auth/session header
442
442
  - request URLs and explicit `--url` values
443
443
  - auth/data-related curl arguments such as `--user`, `--proxy-user`, `--oauth2-bearer`, `--data*`, `--form*`, `--cookie`, `--referer`, `--user-agent`, `--proxy`, `--resolve`, and `--connect-to`
444
+ - Supported `git` substitution contexts include any argument in a top-level `git ...` command. This is intended for private repository operations such as `git clone`, authenticated remotes, `git -c http.extraHeader=... fetch`, commits, and pushes.
444
445
  - Do not assume `{{SECRET:KEY}}` will resolve inside arbitrary shell text.
445
446
  - Do not rely on placeholder substitution in `echo`, `printf`, heredocs, redirects, `tee`, `python -c`, `node -e`, `bash -lc`, `scp`, `wget`, or file-writing commands.
446
- - For non-`curl` secret usage, prefer workflow runtime env injection via `required_secrets`.
447
- - If the currently supported `curl` substitution cases or allowed headers are not sufficient, prefer creating (using the `createWorkflow` tool) or updating a workflow instead of trying to force secret use through bash.
447
+ - For secret usage outside supported `curl` and `git` commands, prefer workflow runtime env injection via `required_secrets`.
448
+ - If the currently supported `curl`/`git` substitution cases or allowed headers are not sufficient, prefer creating (using the `createWorkflow` tool) or updating a workflow instead of trying to force secret use through bash.
448
449
  - If you need to write a script or multi-step automation that uses secrets, prefer creating (using the `createWorkflow` tool) or updating a workflow instead of embedding secret placeholders in shell scripts. In workflows, all declared secrets are injected into the runtime environment (no matter where or how they are used) when you call the `runWorkflow` tool. This is secure since workflow execution is gated by engineer approval.
449
450
  - Do not try to manually replace `{{SECRET:KEY}}` with a raw value yourself.
450
451
  - If you use a secret like `{{SECRET:KEY_NAME}}` and that secret doesn't exist in the user's profile, the tool call will fail, and then you should ask the user to add that key to their Profile Secrets before retrying.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamcopilot",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "A shared AI Agent for Teams",
5
5
  "homepage": "https://teamcopilot.ai",
6
6
  "repository": {