pkg/tool/os: add tasks for environment variables

These are implemented as tasks as they are state
altering and may yield different results on repeated
calls.

Also:
- Remove usage of package errors for pkgs.
- Updated go-cmp
- Fix bug in dependency analysis where default
  values could cause a dependency to be concrete
  prematurely.

Issue #159

Change-Id: I517eb6892cbeff538c806a822510ffce5dcb31b0
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4461
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index 39684db..af08fd4 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -37,6 +37,7 @@
 	_ "cuelang.org/go/pkg/tool/exec"
 	_ "cuelang.org/go/pkg/tool/file"
 	_ "cuelang.org/go/pkg/tool/http"
+	_ "cuelang.org/go/pkg/tool/os"
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
 )
@@ -249,8 +250,11 @@
 			}
 			for _, r := range appendReferences(nil, cr.root, v) {
 				if dep := cr.findTask(r); dep != nil && t != dep {
+					// TODO(string): consider adding dependencies
+					// unconditionally here.
+					// Something like IsFinal would be the right semantics here.
 					v := cr.root.Lookup(r...)
-					if v.IsIncomplete() && v.Kind() != cue.StructKind {
+					if !v.IsConcrete() && v.Kind() != cue.StructKind {
 						t.dep[dep] = true
 					}
 				}
@@ -385,8 +389,9 @@
 	if err != nil {
 		// Lookup kind for backwards compatibility.
 		// TODO: consider at some point whether kind can be removed.
-		kind, err = v.Lookup("kind").String()
-		if err != nil {
+		var err1 error
+		kind, err1 = v.Lookup("kind").String()
+		if err1 != nil {
 			return nil, err
 		}
 	}
diff --git a/cmd/cue/cmd/testdata/script/cmd_env.txt b/cmd/cue/cmd/testdata/script/cmd_env.txt
new file mode 100644
index 0000000..bfda9d4
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_env.txt
@@ -0,0 +1,39 @@
+cue cmd env
+cmp stdout cmd_env.out
+-- cmd_env.out --
+Hello World!
+Hello someone else!
+-- task_tool.cue --
+package home
+
+import (
+    "tool/os"
+    "tool/cli"
+)
+
+command: env: {
+	setenv1: os.Setenv & {
+        TESTNUM: 10
+        MYTEXT:  "World"
+	}
+	env1: os.Getenv & {
+        $after: setenv1
+
+        TESTNUM: *<10 | string
+        MYTEXT:  string
+	}
+	print1: cli.Print & {
+		text: "Hello \(env1.MYTEXT)!"
+	}
+    setenv2: os.Clearenv & {
+        $after: print1
+    }
+    env2: os.Environ & {
+        $after: setenv2
+
+        MYTEXT: string | *"someone else"
+    }
+	print2: cli.Print & {
+		text: "Hello \(env2.MYTEXT)!"
+	}
+}
diff --git a/cue/builtins.go b/cue/builtins.go
index e9eca40..9668515 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -12,7 +12,6 @@
 	"encoding/csv"
 	"encoding/hex"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"html"
 	"io"
@@ -31,6 +30,7 @@
 	"unicode"
 	"unicode/utf8"
 
+	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/literal"
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/internal"
@@ -146,6 +146,21 @@
 	return true, nil
 }
 
+var boolValues = map[string]bool{
+	"1":     true,
+	"0":     false,
+	"t":     true,
+	"f":     false,
+	"T":     true,
+	"F":     false,
+	"true":  true,
+	"false": false,
+	"TRUE":  true,
+	"FALSE": false,
+	"True":  true,
+	"False": false,
+}
+
 var builtinPackages = map[string]*builtinPkg{
 	"": &builtinPkg{
 		native: []*builtin{{}},
@@ -3584,4 +3599,26 @@
 	}
 }`,
 	},
+	"tool/os": &builtinPkg{
+		native: []*builtin{{}},
+		cue: `{
+	Value :: bool | number | *string | null
+	Name ::  !="" & !~"^[$]"
+	Setenv: {
+		[Name]: Value
+		$id:    "tool/os.Setenv"
+	}
+	Getenv: {
+		[Name]: Value
+		$id:    "tool/os.Getenv"
+	}
+	Environ: {
+		[Name]: Value
+		$id:    "tool/os.Environ"
+	}
+	Clearenv: {
+		$id: "tool/os.Clearenv"
+	}
+}`,
+	},
 }
diff --git a/cue/types.go b/cue/types.go
index 02db68c..292aefc 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -923,6 +923,8 @@
 	return pos
 }
 
+// TODO: IsFinal: this value can never be changed.
+
 // IsConcrete reports whether the current value is a concrete scalar value
 // (not relying on default values), a terminal error, a list, or a struct.
 // It does not verify that values of lists or structs are concrete themselves.
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue
index a3353e9..edec7d8 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue
@@ -2134,7 +2134,7 @@
 	containerName?: string @go(ContainerName) @protobuf(1,bytes,opt)
 
 	// Required: resource to select
-	resource: string @go(Resource) @protobuf(2,bytes,opt)
+	"resource": string @go(Resource) @protobuf(2,bytes,opt)
 
 	// Specifies the output format of the exposed resources, defaults to "1"
 	// +optional
@@ -5040,7 +5040,7 @@
 ResourceAttachableVolumesPrefix :: "attachable-volumes-"
 
 // ResourceList is a set of (resource name, quantity) pairs.
-ResourceList :: {[string]: resource.Quantity}
+ResourceList :: [string]: resource.Quantity
 
 // Node is a worker node in Kubernetes.
 // Each node will have a unique identifier in the cache (i.e. in etcd).
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue
index 8a401fa..cf0dbdd 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue
@@ -5,11 +5,7 @@
 package runtime
 
 // NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding.
-NoopEncoder :: {
-	Decoder: Decoder
-}
+NoopEncoder :: "Decoder": Decoder
 
 // NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding.
-NoopDecoder :: {
-	Encoder: Encoder
-}
+NoopDecoder :: "Encoder": Encoder
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue
index 445f642..9ffaf82 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue
@@ -9,12 +9,10 @@
 
 // WithVersionEncoder serializes an object and ensures the GVK is set.
 WithVersionEncoder :: {
-	Version:     GroupVersioner
-	Encoder:     Encoder
-	ObjectTyper: ObjectTyper
+	Version:       GroupVersioner
+	"Encoder":     Encoder
+	"ObjectTyper": ObjectTyper
 }
 
 // WithoutVersionDecoder clears the group version kind of a deserialized object.
-WithoutVersionDecoder :: {
-	Decoder: Decoder
-}
+WithoutVersionDecoder :: "Decoder": Decoder
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue
index 2467f67..a673d37 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue
@@ -58,7 +58,7 @@
 	EncodesAsText: bool
 
 	// Serializer is the individual object serializer for this media type.
-	Serializer: Serializer
+	"Serializer": Serializer
 
 	// PrettySerializer, if set, can serialize this object in a form biased towards
 	// readability.
@@ -75,10 +75,10 @@
 	EncodesAsText: bool
 
 	// Serializer is the top level object serializer for this type when streaming
-	Serializer: Serializer
+	"Serializer": Serializer
 
 	// Framer is the factory for retrieving streams that separate objects on the wire
-	Framer: Framer
+	"Framer": Framer
 }
 
 // NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue
index e20ae6d..2cf6b29 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue
@@ -5,6 +5,4 @@
 package watch
 
 // Recorder records all events that are sent from the watch until it is closed.
-Recorder :: {
-	Interface: Interface
-}
+Recorder :: "Interface": Interface
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
index 34a7ecd..ec357b7 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
@@ -44,11 +44,7 @@
 }
 
 // FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
-FakeWatcher :: {
-	Stopped: bool
-}
+FakeWatcher :: Stopped: bool
 
 // RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
-RaceFreeFakeWatcher :: {
-	Stopped: bool
-}
+RaceFreeFakeWatcher :: Stopped: bool
diff --git a/doc/tutorial/kubernetes/testdata/quick.out b/doc/tutorial/kubernetes/testdata/quick.out
index 95bdcd4..6d4fa10 100644
--- a/doc/tutorial/kubernetes/testdata/quick.out
+++ b/doc/tutorial/kubernetes/testdata/quick.out
@@ -111,8 +111,8 @@
 }
 deployment: {
     bartender: {
-        kind:       "Deployment"
         Name ::     "bartender"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "bartender"
@@ -217,8 +217,8 @@
 }
 deployment: {
     breaddispatcher: {
-        kind:       "Deployment"
         Name ::     "breaddispatcher"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "breaddispatcher"
@@ -323,8 +323,8 @@
 }
 deployment: {
     host: {
-        kind:       "Deployment"
         Name ::     "host"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "host"
@@ -429,8 +429,8 @@
 }
 deployment: {
     maitred: {
-        kind:       "Deployment"
         Name ::     "maitred"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "maitred"
@@ -535,8 +535,8 @@
 }
 deployment: {
     valeter: {
-        kind:       "Deployment"
         Name ::     "valeter"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "valeter"
@@ -641,8 +641,8 @@
 }
 deployment: {
     waiter: {
-        kind:       "Deployment"
         Name ::     "waiter"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "waiter"
@@ -746,8 +746,8 @@
 }
 deployment: {
     waterdispatcher: {
-        kind:       "Deployment"
         Name ::     "waterdispatcher"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "waterdispatcher"
@@ -893,8 +893,8 @@
 }
 deployment: {
     download: {
-        kind:       "Deployment"
         Name ::     "download"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "download"
@@ -1004,8 +1004,8 @@
 }
 statefulSet: {
     etcd: {
-        kind:       "StatefulSet"
         Name ::     "etcd"
+        kind:       "StatefulSet"
         apiVersion: "apps/v1"
         metadata: {
             name: "etcd"
@@ -1176,8 +1176,8 @@
 }
 deployment: {
     events: {
-        kind:       "Deployment"
         Name ::     "events"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "events"
@@ -1311,8 +1311,8 @@
 }
 deployment: {
     tasks: {
-        kind:       "Deployment"
         Name ::     "tasks"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "tasks"
@@ -1429,8 +1429,8 @@
 }
 deployment: {
     updater: {
-        kind:       "Deployment"
         Name ::     "updater"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "updater"
@@ -1543,8 +1543,8 @@
 }
 deployment: {
     watcher: {
-        kind:       "Deployment"
         Name ::     "watcher"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "watcher"
@@ -1698,8 +1698,8 @@
 }
 deployment: {
     caller: {
-        kind:       "Deployment"
         Name ::     "caller"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "caller"
@@ -1841,8 +1841,8 @@
 }
 deployment: {
     dishwasher: {
-        kind:       "Deployment"
         Name ::     "dishwasher"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "dishwasher"
@@ -1984,8 +1984,8 @@
 }
 deployment: {
     expiditer: {
-        kind:       "Deployment"
         Name ::     "expiditer"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "expiditer"
@@ -2118,8 +2118,8 @@
 }
 deployment: {
     headchef: {
-        kind:       "Deployment"
         Name ::     "headchef"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "headchef"
@@ -2252,8 +2252,8 @@
 }
 deployment: {
     linecook: {
-        kind:       "Deployment"
         Name ::     "linecook"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "linecook"
@@ -2386,8 +2386,8 @@
 }
 deployment: {
     pastrychef: {
-        kind:       "Deployment"
         Name ::     "pastrychef"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "pastrychef"
@@ -2520,8 +2520,8 @@
 }
 deployment: {
     souschef: {
-        kind:       "Deployment"
         Name ::     "souschef"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "souschef"
@@ -2708,8 +2708,8 @@
 }
 deployment: {
     alertmanager: {
-        kind:       "Deployment"
         Name ::     "alertmanager"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "alertmanager"
@@ -2832,8 +2832,8 @@
 }
 deployment: {
     grafana: {
-        kind:       "Deployment"
         Name ::     "grafana"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "grafana"
@@ -2972,8 +2972,8 @@
 }
 daemonSet: {
     "node-exporter": {
-        kind:       "DaemonSet"
         Name ::     "node-exporter"
+        kind:       "DaemonSet"
         apiVersion: "apps/v1"
         metadata: {
             name: "node-exporter"
@@ -3352,8 +3352,8 @@
 }
 deployment: {
     prometheus: {
-        kind:       "Deployment"
         Name ::     "prometheus"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "prometheus"
@@ -3585,8 +3585,8 @@
 }
 deployment: {
     authproxy: {
-        kind:       "Deployment"
         Name ::     "authproxy"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "authproxy"
@@ -3699,8 +3699,8 @@
 }
 deployment: {
     goget: {
-        kind:       "Deployment"
         Name ::     "goget"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "goget"
@@ -3983,8 +3983,8 @@
 }
 deployment: {
     nginx: {
-        kind:       "Deployment"
         Name ::     "nginx"
+        kind:       "Deployment"
         apiVersion: "apps/v1"
         metadata: {
             name: "nginx"
diff --git a/go.mod b/go.mod
index 68b2e98..dafaba4 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@
 	github.com/davecgh/go-spew v1.1.0 // indirect
 	github.com/emicklei/proto v1.6.15
 	github.com/ghodss/yaml v1.0.0
-	github.com/google/go-cmp v0.2.0
+	github.com/google/go-cmp v0.3.1
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/kr/pretty v0.1.0
 	github.com/kylelemons/godebug v1.1.0
diff --git a/go.sum b/go.sum
index 6cc3c30..41e0c47 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
diff --git a/pkg/regexp/manual.go b/pkg/regexp/manual.go
index 1a14476..c9201d1 100644
--- a/pkg/regexp/manual.go
+++ b/pkg/regexp/manual.go
@@ -15,8 +15,9 @@
 package regexp
 
 import (
-	"errors"
 	"regexp"
+
+	"cuelang.org/go/cue/errors"
 )
 
 var errNoMatch = errors.New("no match")
diff --git a/pkg/tool/exec/exec.go b/pkg/tool/exec/exec.go
index 1bdca9a..6146e53 100644
--- a/pkg/tool/exec/exec.go
+++ b/pkg/tool/exec/exec.go
@@ -17,12 +17,12 @@
 //go:generate go run gen.go
 
 import (
-	"errors"
 	"fmt"
 	"os/exec"
 	"strings"
 
 	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/internal/task"
 	"golang.org/x/xerrors"
 )
diff --git a/pkg/tool/os/doc.go b/pkg/tool/os/doc.go
new file mode 100644
index 0000000..b32321f
--- /dev/null
+++ b/pkg/tool/os/doc.go
@@ -0,0 +1,48 @@
+// Code generated by cue get go. DO NOT EDIT.
+
+// Package os defines tasks for retrieving os-related information.
+//
+// CUE definitions:
+//
+//     // A Value are all possible values allowed in flags.
+//     // A null value unsets an environment variable.
+//     Value :: bool | number | *string | null
+//
+//     // Name indicates a valid flag name.
+//     Name :: !="" & !~"^[$]"
+//
+//     // Setenv defines a set of command line flags, the values of which will be set
+//     // at run time. The doc comment of the flag is presented to the user in help.
+//     //
+//     // To define a shorthand, define the shorthand as a new flag referring to
+//     // the flag of which it is a shorthand.
+//     Setenv: {
+//         $id: "tool/os.Setenv"
+//
+//         [Name]: Value
+//     }
+//
+//     // Getenv gets and parses the specific command line variables.
+//     Getenv: {
+//         $id: "tool/os.Getenv"
+//
+//         [Name]: Value
+//     }
+//
+//     // Environ populates a struct with all environment variables.
+//     Environ: {
+//         $id: "tool/os.Environ"
+//
+//         // A map of all populated values.
+//         // Individual entries may be specified ahead of time to enable
+//         // validation and parsing. Values that are marked as required
+//         // will fail the task if they are not found.
+//         [Name]: Value
+//     }
+//
+//     // Clearenv clears all environment variables.
+//     Clearenv: {
+//         $id: "tool/os.Clearenv"
+//     }
+//
+package os
diff --git a/pkg/tool/os/env.go b/pkg/tool/os/env.go
new file mode 100644
index 0000000..487d397
--- /dev/null
+++ b/pkg/tool/os/env.go
@@ -0,0 +1,274 @@
+// Copyright 2019 CUE 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.
+
+package os
+
+//go:generate go run gen.go
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/task"
+)
+
+func init() {
+	task.Register("tool/os.Setenv", newSetenvCmd)
+	task.Register("tool/os.Getenv", newGetenvCmd)
+	task.Register("tool/os.Environ", newEnvironCmd)
+	task.Register("tool/os.Clearenv", newClearenvCmd)
+
+	// TODO:
+	// Tasks:
+	// - Exit?
+	// - Getwd/ Setwd (or in tool/file?)
+
+	// Functions:
+	// - Hostname
+	// - UserCache/Home/Config (or in os/user?)
+}
+
+type clearenvCmd struct{}
+
+func newClearenvCmd(v cue.Value) (task.Runner, error) {
+	return &clearenvCmd{}, nil
+}
+
+func (c *clearenvCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+	os.Clearenv()
+	return map[string]interface{}{}, nil
+}
+
+type setenvCmd struct{}
+
+func newSetenvCmd(v cue.Value) (task.Runner, error) {
+	return &setenvCmd{}, nil
+}
+
+func (c *setenvCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+	iter, err := v.Fields()
+	if err != nil {
+		return nil, err
+	}
+
+	for iter.Next() {
+		name := iter.Label()
+		if strings.HasPrefix(name, "$") {
+			continue
+		}
+
+		v, _ := iter.Value().Default()
+
+		if !v.IsConcrete() {
+			return nil, errors.Newf(v.Pos(),
+				"non-concrete environment variable %s", name)
+		}
+		switch k := v.IncompleteKind(); k {
+		case cue.ListKind, cue.StructKind:
+			return nil, errors.Newf(v.Pos(),
+				"unsupported type %s for environment variable %s", k, name)
+
+		case cue.NullKind:
+			err = os.Unsetenv(name)
+
+		case cue.BoolKind:
+			if b, _ := v.Bool(); b {
+				err = os.Setenv(name, "1")
+			} else {
+				err = os.Setenv(name, "0")
+			}
+
+		case cue.StringKind:
+			s, _ := v.String()
+			err = os.Setenv(name, s)
+
+		default:
+			err = os.Setenv(name, fmt.Sprint(v))
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return map[string]interface{}{}, err
+}
+
+type getenvCmd struct{}
+
+func newGetenvCmd(v cue.Value) (task.Runner, error) {
+	return &getenvCmd{}, nil
+}
+
+func (c *getenvCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+	iter, err := v.Fields()
+	if err != nil {
+		return nil, err
+	}
+
+	update := map[string]interface{}{}
+
+	for iter.Next() {
+		name := iter.Label()
+		if strings.HasPrefix(name, "$") {
+			continue
+		}
+		v := iter.Value()
+
+		if err := validateEntry(name, v); err != nil {
+			return nil, err
+		}
+
+		str, ok := os.LookupEnv(name)
+		if !ok {
+			update[name] = nil
+			continue
+		}
+		x, err := fromString(name, str, v)
+		if err != nil {
+			return nil, err
+		}
+		update[name] = x
+	}
+
+	return update, nil
+}
+
+type environCmd struct{}
+
+func newEnvironCmd(v cue.Value) (task.Runner, error) {
+	return &environCmd{}, nil
+}
+
+func (c *environCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+	iter, err := v.Fields()
+	if err != nil {
+		return nil, err
+	}
+
+	update := map[string]interface{}{}
+
+	for _, kv := range os.Environ() {
+		a := strings.SplitN(kv, "=", 2)
+
+		name := a[0]
+		str := a[1]
+
+		if v := v.Lookup(name); v.Exists() {
+			update[name], err = fromString(name, str, v)
+			if err != nil {
+				return nil, err
+			}
+		} else {
+			update[name] = str
+		}
+	}
+
+	for iter.Next() {
+		name := iter.Label()
+		if strings.HasPrefix(name, "$") {
+			continue
+		}
+		if err := validateEntry(name, iter.Value()); err != nil {
+			return nil, err
+		}
+		if _, ok := update[name]; !ok {
+			update[name] = nil
+		}
+	}
+
+	return update, nil
+}
+
+func validateEntry(name string, v cue.Value) error {
+	if k := v.IncompleteKind(); k&^(cue.NumberKind|cue.NullKind|cue.BoolKind|cue.StringKind) != 0 {
+		return errors.Newf(v.Pos(),
+			"invalid type %s for environment variable %s", k, name)
+	}
+	return nil
+}
+
+func fromString(name, str string, v cue.Value) (x interface{}, err error) {
+	k := v.IncompleteKind()
+
+	var expr ast.Expr
+	var errs errors.Error
+
+	if k&cue.NumberKind != 0 {
+		expr, err = parser.ParseExpr(name, str)
+		if err != nil {
+			errs = errors.Wrapf(err, v.Pos(),
+				"invalid number for environment variable %s", name)
+		}
+	}
+
+	if k&cue.BoolKind != 0 {
+		str = strings.TrimSpace(str)
+		b, ok := boolValues[str]
+		if !ok {
+			errors.Append(errs, errors.Newf(v.Pos(),
+				"invalid boolean value %q for environment variable %s", str, name))
+		} else if expr != nil || k&cue.StringKind != 0 {
+			// Convert into an expression
+			bl := ast.NewBool(b)
+			if expr != nil {
+				expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: bl}
+			} else {
+				expr = bl
+			}
+		} else {
+			x = b
+		}
+	}
+
+	if k&cue.StringKind != 0 {
+		if expr != nil {
+			expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: ast.NewString(str)}
+		} else {
+			x = str
+		}
+	}
+
+	switch {
+	case expr != nil:
+		return expr, nil
+	case x != nil:
+		return x, nil
+	case errs == nil:
+		return nil, errors.Newf(v.Pos(),
+			"invalid type for environment variable %s", name)
+	}
+	return nil, errs
+}
+
+var boolValues = map[string]bool{
+	"1":     true,
+	"0":     false,
+	"t":     true,
+	"f":     false,
+	"T":     true,
+	"F":     false,
+	"true":  true,
+	"false": false,
+	"TRUE":  true,
+	"FALSE": false,
+	"True":  true,
+	"False": false,
+}
diff --git a/pkg/tool/os/env_test.go b/pkg/tool/os/env_test.go
new file mode 100644
index 0000000..5b426b7
--- /dev/null
+++ b/pkg/tool/os/env_test.go
@@ -0,0 +1,182 @@
+// Copyright 2019 CUE 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.
+
+package os
+
+import (
+	"os"
+	"testing"
+
+	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/task"
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+)
+
+func TestSetenv(t *testing.T) {
+	os.Setenv("CUEOSTESTUNSET", "SET")
+	v := parse(t, "tool/os.Setenv", `{
+		CUEOSTESTMOOD:  "yippie"
+		CUEOSTESTTRUE:  true
+		CUEOSTESTFALSE: false
+		CUEOSTESTNUM:   34K
+		CUEOSTESTUNSET: null
+	}`)
+	_, err := (*setenvCmd).Run(nil, nil, v)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, p := range [][2]string{
+		{"CUEOSTESTMOOD", "yippie"},
+		{"CUEOSTESTTRUE", "1"},
+		{"CUEOSTESTFALSE", "0"},
+		{"CUEOSTESTNUM", "34000"},
+	} {
+		got := os.Getenv(p[0])
+		if got != p[1] {
+			t.Errorf("got %v; want %v", got, p[1])
+		}
+	}
+
+	if _, ok := os.LookupEnv("CUEOSTESTUNSET"); ok {
+		t.Error("CUEOSTESTUNSET should have been unset")
+	}
+
+	v = parse(t, "tool/os.Setenv", `{
+		CUEOSTESTMOOD: string
+	}`)
+	_, err = (*setenvCmd).Run(nil, nil, v)
+	if err == nil {
+		t.Fatal("expected incomplete error")
+	}
+	// XXX: ensure error is not concrete.
+}
+
+func TestGetenv(t *testing.T) {
+
+	for _, p := range [][2]string{
+		{"CUEOSTESTMOOD", "yippie"},
+		{"CUEOSTESTTRUE", "True"},
+		{"CUEOSTESTFALSE", "0"},
+		{"CUEOSTESTBI", "1"},
+		{"CUEOSTESTNUM", "34K"},
+		{"CUEOSTESTNUMD", "not a num"},
+		{"CUEOSTESTMULTI", "10"},
+	} {
+		os.Setenv(p[0], p[1])
+	}
+
+	config := `{
+		CUEOSTESTMOOD:  string
+		CUEOSTESTTRUE:  bool
+		CUEOSTESTFALSE: bool | string
+		CUEOSTESTBI: 	*bool | int,
+		CUEOSTESTNUM:   int
+		CUEOSTESTNUMD:  *int | *bool | string
+		CUEOSTESTMULTI: *<10 | string
+		CUEOSTESTNULL:  int | null
+	}`
+
+	want := map[string]interface{}{
+		"CUEOSTESTMOOD": "yippie",
+		"CUEOSTESTTRUE": true,
+		"CUEOSTESTFALSE": &ast.BinaryExpr{
+			Op: token.OR,
+			X:  ast.NewBool(false),
+			Y:  ast.NewString("0"),
+		},
+		"CUEOSTESTBI": &ast.BinaryExpr{
+			Op: token.OR,
+			X:  &ast.BasicLit{Kind: token.INT, Value: "1"},
+			Y:  ast.NewBool(true),
+		},
+		"CUEOSTESTNUM":  &ast.BasicLit{Kind: token.INT, Value: "34K"},
+		"CUEOSTESTNUMD": "not a num",
+		"CUEOSTESTMULTI": &ast.BinaryExpr{
+			Op: token.OR,
+			X:  &ast.BasicLit{Kind: token.INT, Value: "10"},
+			Y:  ast.NewString("10"),
+		},
+		"CUEOSTESTNULL": nil,
+	}
+
+	for _, tc := range []struct {
+		pkg    string
+		runner task.Runner
+	}{
+		{"tool/os.Getenv", &getenvCmd{}},
+		{"tool/os.Environ", &environCmd{}},
+	} {
+		v := parse(t, tc.pkg, config)
+		got, err := tc.runner.Run(nil, v)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		var opts = []cmp.Option{
+			cmpopts.IgnoreFields(ast.BinaryExpr{}, "OpPos"),
+			cmpopts.IgnoreFields(ast.BasicLit{}, "ValuePos"),
+			cmpopts.IgnoreUnexported(ast.BasicLit{}, ast.BinaryExpr{}),
+			// For ignoring addinonal entries from os.Environ:
+			cmpopts.IgnoreMapEntries(func(s string, x interface{}) bool {
+				_, ok := want[s]
+				return !ok
+			}),
+		}
+
+		if !cmp.Equal(got, want, opts...) {
+			t.Error(cmp.Diff(got, want, opts...))
+		}
+
+		// Errors:
+		for _, etc := range []struct{ config, err string }{{
+			config: `{ CUEOSTESTNULL:  [...string] }`,
+			err:    "expected unsupported type error",
+		}, {
+			config: `{ CUEOSTESTNUMD: int }`,
+			err:    "expected invalid number error",
+		}, {
+			config: `{ CUEOSTESTNUMD: null }`,
+			err:    "expected invalid type",
+		}} {
+			t.Run(etc.err, func(t *testing.T) {
+				v = parse(t, tc.pkg, etc.config)
+				if _, err = tc.runner.Run(nil, v); err == nil {
+					t.Error(etc.err)
+				}
+			})
+		}
+	}
+}
+
+func parse(t *testing.T, kind, expr string) cue.Value {
+	t.Helper()
+
+	x, err := parser.ParseExpr("test", expr)
+	if err != nil {
+		errors.Print(os.Stderr, err, nil)
+		t.Fatal(err)
+	}
+	var r cue.Runtime
+	i, err := r.CompileExpr(x)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return internal.UnifyBuiltin(i.Value(), kind).(cue.Value)
+}
diff --git a/pkg/tool/os/gen.go b/pkg/tool/os/gen.go
new file mode 100644
index 0000000..b6df11a
--- /dev/null
+++ b/pkg/tool/os/gen.go
@@ -0,0 +1,47 @@
+// Copyright 2019 CUE 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.
+
+// +build ignore
+
+package main
+
+// TODO: remove when we have a cuedoc server. Until then,
+// piggyback on godoc.org.
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+)
+
+const msg = `// Code generated by cue get go. DO NOT EDIT.
+
+// Package os defines tasks for retrieving os-related information.
+//
+// CUE definitions:
+//     %s
+package os
+`
+
+func main() {
+	f, _ := os.Create("doc.go")
+	defer f.Close()
+	b, _ := ioutil.ReadFile("os.cue")
+	i := bytes.Index(b, []byte("package os"))
+	b = b[i+len("package os")+1:]
+	b = bytes.ReplaceAll(b, []byte("\n"), []byte("\n//     "))
+	b = bytes.ReplaceAll(b, []byte("\t"), []byte("    "))
+	fmt.Fprintf(f, msg, string(b))
+}
diff --git a/pkg/tool/os/os.cue b/pkg/tool/os/os.cue
new file mode 100644
index 0000000..d7e4322
--- /dev/null
+++ b/pkg/tool/os/os.cue
@@ -0,0 +1,56 @@
+// Copyright 2019 The CUE 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.
+
+package os
+
+// A Value are all possible values allowed in flags.
+// A null value unsets an environment variable.
+Value :: bool | number | *string | null
+
+// Name indicates a valid flag name.
+Name :: !="" & !~"^[$]"
+
+// Setenv defines a set of command line flags, the values of which will be set
+// at run time. The doc comment of the flag is presented to the user in help.
+//
+// To define a shorthand, define the shorthand as a new flag referring to
+// the flag of which it is a shorthand.
+Setenv: {
+	$id: "tool/os.Setenv"
+
+	[Name]: Value
+}
+
+// Getenv gets and parses the specific command line variables.
+Getenv: {
+	$id: "tool/os.Getenv"
+
+	[Name]: Value
+}
+
+// Environ populates a struct with all environment variables.
+Environ: {
+	$id: "tool/os.Environ"
+
+	// A map of all populated values.
+	// Individual entries may be specified ahead of time to enable
+	// validation and parsing. Values that are marked as required
+	// will fail the task if they are not found.
+	[Name]: Value
+}
+
+// Clearenv clears all environment variables.
+Clearenv: {
+	$id: "tool/os.Clearenv"
+}