diff --git a/pkg/misc/genjsdeps/genjsdeps.go b/pkg/misc/genjsdeps/genjsdeps.go
new file mode 100644
index 000000000..57995d75f
--- /dev/null
+++ b/pkg/misc/genjsdeps/genjsdeps.go
@@ -0,0 +1,150 @@
+/*
+Copyright 2013 The Camlistore Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// The genjsdeps command, similarly to the closure depswriter.py tool,
+// outputs to os.Stdout for each .js file, which namespaces
+// it provides, and the namespaces it requires, hence allowing
+// the closure library to resolve dependencies between those files.
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "sync"
+ "time"
+)
+
+// TODO(mpl): make a library and a separate command which uses
+// that library.
+// http://camlistore.org/issue/142
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "Usage: genjsdeps
\n")
+ os.Exit(1)
+}
+
+func main() {
+ flag.Parse()
+ args := flag.Args()
+ if len(args) != 1 {
+ usage()
+ }
+ dir := path.Clean(args[0])
+ fi, err := os.Stat(dir)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if !fi.IsDir() {
+ log.Fatalf("%v not a dir", dir)
+ }
+ var buf bytes.Buffer
+ err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !strings.HasSuffix(path, ".js") {
+ return nil
+ }
+ suffix := path[len(dir)+1:]
+ prov, req, err := parseProvidesRequires(info, path)
+ if err != nil {
+ return err
+ }
+ if len(prov) > 0 {
+ fmt.Fprintf(&buf, "goog.addDependency(%q, %v, %v);\n", "../../"+suffix, jsList(prov), jsList(req))
+ }
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("Error walking %d generating deps.js: %v", dir, err)
+ }
+ io.Copy(os.Stdout, &buf)
+}
+
+var provReqRx = regexp.MustCompile(`^goog\.(provide|require)\(['"]([\w\.]+)['"]\)`)
+
+type depCacheItem struct {
+ modTime time.Time
+ provides, requires []string
+}
+
+var (
+ depCacheMu sync.Mutex
+ depCache = map[string]depCacheItem{}
+)
+
+func parseProvidesRequires(fi os.FileInfo, path string) (provides, requires []string, err error) {
+ mt := fi.ModTime()
+ depCacheMu.Lock()
+ defer depCacheMu.Unlock()
+ if ci := depCache[path]; ci.modTime.Equal(mt) {
+ return ci.provides, ci.requires, nil
+ }
+
+ f, err := os.Open(path)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ br := bufio.NewReader(f)
+ for {
+ l, err := br.ReadString('\n')
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+ if !strings.HasPrefix(l, "goog.") {
+ continue
+ }
+ m := provReqRx.FindStringSubmatch(l)
+ if m != nil {
+ if m[1] == "provide" {
+ provides = append(provides, m[2])
+ } else {
+ requires = append(requires, m[2])
+ }
+ }
+ }
+ depCache[path] = depCacheItem{provides: provides, requires: requires, modTime: mt}
+ return provides, requires, nil
+}
+
+// jsList prints a list of strings as JavaScript list.
+type jsList []string
+
+func (s jsList) String() string {
+ var buf bytes.Buffer
+ buf.WriteByte('[')
+ for i, v := range s {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ fmt.Fprintf(&buf, "%q", v)
+ }
+ buf.WriteByte(']')
+ return buf.String()
+}